Kaynağa Gözat

add login and speech to text

andy 9 ay önce
ebeveyn
işleme
6902799b0f
66 değiştirilmiş dosya ile 51205 ekleme ve 76 silme
  1. BIN
      .DS_Store
  2. 64 4
      css/custom.css
  3. BIN
      img/.DS_Store
  4. BIN
      img/microphone.png
  5. BIN
      img/new-moon.png
  6. BIN
      img/stop-button.png
  7. 87 12
      index.html
  8. BIN
      js/.DS_Store
  9. 2 0
      js/demo/microphone.js
  10. 485 46
      js/demo/text-to-chart.js
  11. 59 0
      js/login.js
  12. 49 0
      js/register.js
  13. 23 14
      login.html
  14. 89 0
      register.html
  15. 468 0
      src/app-support/app-miniProgram-wx-support.js
  16. 209 0
      src/app-support/app-native-support.js
  17. 450 0
      src/app-support/app.js
  18. 471 0
      src/engine/beta-amr-engine.js
  19. 359 0
      src/engine/beta-amr.js
  20. 202 0
      src/engine/beta-ogg-engine.js
  21. 367 0
      src/engine/beta-ogg.js
  22. 86 0
      src/engine/beta-webm.js
  23. 236 0
      src/engine/g711x.js
  24. 11401 0
      src/engine/mp3-engine.js
  25. 533 0
      src/engine/mp3.js
  26. 174 0
      src/engine/pcm.js
  27. 122 0
      src/engine/wav.js
  28. 910 0
      src/extensions/asr.aliyun.short.js
  29. 860 0
      src/extensions/buffer_stream.player.js
  30. 372 0
      src/extensions/create-audio.nmn2pcm.js
  31. 268 0
      src/extensions/dtmf.decode.js
  32. 196 0
      src/extensions/dtmf.encode.js
  33. 377 0
      src/extensions/frequency.histogram.view.js
  34. 118 0
      src/extensions/lib.fft.js
  35. 1155 0
      src/extensions/sonic.js
  36. 278 0
      src/extensions/wavesurfer.view.js
  37. 229 0
      src/extensions/waveview.js
  38. 915 0
      src/i18n/Template.js
  39. 766 0
      src/i18n/en-US.js
  40. 915 0
      src/i18n/es.js
  41. 915 0
      src/i18n/fr.js
  42. 41 0
      src/i18n/zh-CN.js
  43. 275 0
      src/package-build.js
  44. 475 0
      src/package-i18n.js
  45. 29 0
      src/package-lock.json
  46. 10 0
      src/package.json
  47. 1849 0
      src/recorder-core.js
  48. BIN
      vendor/.DS_Store
  49. BIN
      vendor/bootstrap/.DS_Store
  50. 4177 0
      vendor/bootstrap/scss/bootstrap-grid.css
  51. 2 0
      vendor/bootstrap/scss/bootstrap-grid.css.map
  52. 327 0
      vendor/bootstrap/scss/bootstrap-reboot.css
  53. 2 0
      vendor/bootstrap/scss/bootstrap-reboot.css.map
  54. 10840 0
      vendor/bootstrap/scss/bootstrap.css
  55. 2 0
      vendor/bootstrap/scss/bootstrap.css.map
  56. BIN
      vendor/fontawesome-free/.DS_Store
  57. 18 0
      vendor/fontawesome-free/scss/brands.css
  58. 10 0
      vendor/fontawesome-free/scss/brands.css.map
  59. 6091 0
      vendor/fontawesome-free/scss/fontawesome.css
  60. 2 0
      vendor/fontawesome-free/scss/fontawesome.css.map
  61. 18 0
      vendor/fontawesome-free/scss/regular.css
  62. 10 0
      vendor/fontawesome-free/scss/regular.css.map
  63. 19 0
      vendor/fontawesome-free/scss/solid.css
  64. 10 0
      vendor/fontawesome-free/scss/solid.css.map
  65. 2786 0
      vendor/fontawesome-free/scss/v4-shims.css
  66. 2 0
      vendor/fontawesome-free/scss/v4-shims.css.map

BIN
.DS_Store


+ 64 - 4
css/custom.css

@@ -153,14 +153,24 @@ background-position: center;
     /* padding-left: 1.5rem;
     padding-right: 1.5rem; */
     position: fixed;
-    bottom:-5px;
+    bottom:5px;
     width: 95%;
     left: 25px;
+    padding: 1rem;
    
 }
 
+#recording_block{
+    position: fixed;
+    bottom:5px;
+    width: 95%;
+    left: 25px;
+    padding: 1rem;
+    z-index: 99;
+}
+
 #fixed_button .input-group{
-  background: #fff;
+  /* background: #fff; */
   padding: 1.5rem 1rem;
 }
 
@@ -191,7 +201,7 @@ background-position: center;
 }
 
 .show {
-    right: 10px; /* 显示侧边栏 */
+    right: 0px; /* 显示侧边栏 */
 }
 
 .hidden {
@@ -209,7 +219,7 @@ background-position: center;
     padding: 20px;
 }
 
-#style_setting_close,#style_setting_close_table{
+#style_setting_close,#style_setting_close_table,#style_setting_close{
     font-size: 20px;
     cursor: pointer;
 }
@@ -286,4 +296,54 @@ background-position: center;
 
 #downloadButton{
     display: none;
+}
+
+#logout{
+    cursor: pointer;
+}
+
+.microphone{
+  /* margin-top: 1rem; */
+  cursor: pointer;
+  transition: 0.3s;
+}
+
+.microphone:hover{
+    opacity: 0.8;
+}
+
+.speechToText{
+    background: none;
+    border: none;
+}
+
+.circle-div {
+    width: 100px;
+    height: 100px;
+    border-radius: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    background-color: #4CAF50;
+    overflow: hidden;
+    cursor: pointer;
+    transition: 0.3s;
+}
+
+.circle-div:hover{
+    opacity: 0.8;
+}
+
+.circle-div img {
+    width: 50%;
+    height: 50%;
+}
+
+.recording{
+    font-size: 32px;
+}
+
+#stop{
+    background: none;
+    border:none;
 }

BIN
img/.DS_Store


BIN
img/microphone.png


BIN
img/new-moon.png


BIN
img/stop-button.png


+ 87 - 12
index.html

@@ -23,6 +23,7 @@
     <link href="css/custom.css" rel="stylesheet">
 
 
+
 </head>
 
 <body id="page-top">
@@ -108,9 +109,16 @@
                         <li class="nav-item dropdown no-arrow">
                             <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
                                 data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
-                                <span class="mr-2 d-none d-lg-inline text-gray-600 small">User</span>
+                                <span class="mr-2 d-none d-lg-inline text-gray-600 small" id="userName"></span>
                                 <img class="img-profile rounded-circle" src="img/undraw_profile.svg">
                             </a>
+                            <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
+                                aria-labelledby="userDropdown">
+                                <a id="logout" class="dropdown-item" data-toggle="modal" data-target="#logoutModal">
+                                    <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
+                                    登出
+                                </a>
+                            </div>
                         </li>
                     </ul>
                 </nav>
@@ -215,21 +223,69 @@
                     </div>
                 </div>
                 <!-- /.container-fluid -->
-                <div id="fixed_button">
+                <div style="background: #fff;" id="fixed_button">
                     <!-- <div> -->
-                    <div class="input-group">
-                        <input type="text" id="keyword_data" class="form-control bg-white small" placeholder="請輸入關鍵字..."
-                            aria-label="Search" aria-describedby="basic-addon2">
-                        <div id="suggestions" class="autocomplete-suggestions"></div>
-                        <div class="input-group-append">
-                            <button class="btn btn-primary" id="send_data" type="button">
-                                <!-- <i class="fas fa-search fa-sm"></i> -->
-                                送出
-                            </button>
+                    <div class="d-flex">
+                        <div class="input-group">
+                            <input type="text" id="keyword_data" class="form-control bg-white small"
+                                placeholder="請輸入關鍵字..." aria-label="Search" aria-describedby="basic-addon2">
+                            <div id="suggestions" class="autocomplete-suggestions"></div>
+                            <div class="input-group-append">
+                                <button class="btn btn-primary" id="send_data" type="button">
+                                    <!-- <i class="fas fa-search fa-sm"></i> -->
+                                    送出
+                                </button>
+
+                            </div>
                         </div>
+                        <button id="recording_button" class="speechToText" type="button">
+                            <img class="microphone" width="40" height="40" src="/img/microphone.png" alt="microphone">
+                        </button>
+
                     </div>
+
+
                     <!-- </div> -->
                 </div>
+
+                <div style="background: #fff;" id="recording_block" class="hidden">
+                    <div class="text-right">
+                        <i id="recording_block_close" class="fa fa-times"></i>
+                    </div>
+                    <div class="row">
+                        <div class="col-lg-6 d-flex justify-content-center align-items-center">
+                            <p class="recording mb-0">錄音中....</p>
+                            <div class="mainBox d-none">
+                                <div class="reclog"></div>
+                            </div>
+                            <div style="padding-top:5px">
+                                <!-- <div style="border:1px solid #ccc;display:inline-block;vertical-align:bottom">
+                                    <div style="height:100px;width:300px;" class="recwave"></div>
+                                </div> -->
+                                <div class="ml-5" style="height:40px;width:300px;background:#fff;position:relative;">
+                                    <div class="recpowerx" style="height:40px;background:#0B1;position:absolute;"></div>
+                                    <div class="recpowert"
+                                        style="padding-left:50px; line-height:40px; position: relative;">
+                                    </div>
+                                </div>
+                                <!-- <button onclick="recPlay()" reclang="uwNo">播放</button> -->
+                            </div>
+                            <audio class="recPlay" controls style="display:none;width:100%"></audio>
+
+                        </div>
+                        <div class="col-lg-6 d-flex justify-content-center">
+                            <button id="stop">
+                                <div class="circle-div">
+                                    <img src="./img/stop-button.png" alt="">
+                                </div>
+                            </button>
+
+                        </div>
+                    </div>
+
+
+
+                </div>
             </div>
             <!-- End of Main Content -->
             <!-- <div id="fixed_button">
@@ -428,7 +484,10 @@
 
                 <img class="bgImg ml-2" src="./img/bg07.webp" width="80" height="60" value="./img/bg07.webp" alt="">
 
-
+                <div class="bgImg ml-2 d-flex align-items-center justify-content-center" value="" width="80" height="80"
+                    style="border:1px solid #e3e6f0;">
+                    <p style="width: 80px" class="mb-0 text-center">無</p>
+                </div>
 
             </div>
         </form>
@@ -477,9 +536,25 @@
     <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
 
+
+
+
+
+
+    <script src="./src/recorder-core.js"></script> <!--必须引入的录音核心-->
+    <script src="./src/engine/mp3.js"></script> <!--相应格式支持文件-->
+    <script src="./src/engine/mp3-engine.js"></script> <!--如果此格式有额外的编码引擎的话,也要加上-->
+    <script src="./src/extensions/frequency.histogram.view.js"></script>
+
+    <script src="./src/extensions/lib.fft.js"></script>
+
     <script src="./js/demo/text-to-chart.js"></script>
 
 
+    <!-- <script src="./js/demo/microphone.js"></script> -->
+
+
+
 </body>
 
 </html>

BIN
js/.DS_Store


+ 2 - 0
js/demo/microphone.js

@@ -0,0 +1,2 @@
+
+

+ 485 - 46
js/demo/text-to-chart.js

@@ -2,10 +2,26 @@
 
 console.log('text-to-chart');
 
+var accessToken = localStorage.getItem("access_token");
+
+var usernameEmail = localStorage.getItem("username");
+
+
+
+let username = usernameEmail.split('@')[0];
+
+console.log(username);
+
+console.log(accessToken);
+
+
 Chart.register(ChartDataLabels);
 
+var userNameSpan = document.getElementById("userName");
 
 
+userNameSpan.textContent = username;
+
 var inputField = document.getElementById("keyword_data");
 
 var sendButton = document.getElementById("send_data");
@@ -1499,77 +1515,89 @@ function get_data(input_text_value) {
         myChart.destroy();
         document.getElementById('img_box_url').src = "";
     }
-    axios.get('https://cmm.ai:8080/answer?question=' + input_text_value)
+    axios.get('https://cmm.ai:8080/answer_with_token?token=' + accessToken + '&question=' + input_text_value)
         .then(response => {
 
             apiHideLoading();
 
-            // 在這裡處理成功獲取 JSON 的情況
             console.log(response);
 
-            var chart_info = response.data.chart_info.Title;
 
-            chartType = response.data.chart_info.Chart_type;
+            if (response.data.data == "Failed to generate chart!") {
+                alert('無法生成圖表')
+                return
+            } else {
+                var chart_info = response.data.chart_info.Title;
+
+                chartType = response.data.chart_info.Chart_type;
 
-            var finance_img_url = response.data.imageUrl_info.imageUrl;
-            if (response.data.imageUrl_info != null) {
-                console.log(finance_img_url);
-                document.getElementById('img_box_url').src = finance_img_url;
-            }
+                var finance_img_url = response.data.imageUrl_info.imageUrl;
+                if (response.data.imageUrl_info != null) {
+                    console.log(finance_img_url);
+                    document.getElementById('img_box_url').src = finance_img_url;
+                }
+
+                keywordInput.value = chart_info;
+
+                chartTitle.textContent = chart_info;
 
+                unit = response.data.chart_info.Unit_of_dependent_variable;
 
-            // switch (chartType) {
-            //     case "line":
-            //         document.getElementById("inlineRadio4").checked = true; // 折线图
-            //         break;
-            //     case "bar":
-            //         document.getElementById("inlineRadio5").checked = true; // 柱状图
-            //         labelDependent = response.data.chart_info.Label_dependent_variable;
-            //         break;
-            //     case "pie":
-            //         document.getElementById("inlineRadio6").checked = true; // 圆饼图
-            //         break;
-            //     case "table":
-            //         document.getElementById("inlineRadio7").checked = true; // 表格
-            //         break;
-            //     default:
-            //     // 默认情况
-            // }
+                var Label_dependent_variable = response.data.chart_info.Label_dependent_variable;
 
-            keywordInput.value = chart_info;
+                var Label_independent_variable = response.data.chart_info.Label_independent_variable;
 
-            chartTitle.textContent = chart_info;
 
-            unit = response.data.chart_info.Unit_of_dependent_variable;
+                unit_data.value = response.data.chart_info.Unit_of_dependent_variable;
 
-            var Label_dependent_variable = response.data.chart_info.Label_dependent_variable;
 
-            var Label_independent_variable = response.data.chart_info.Label_independent_variable;
+                dataArray = response.data.data;
 
 
-            unit_data.value = response.data.chart_info.Unit_of_dependent_variable;
+                if (chartType === "table") {
+                    createTable(dataArray, unit, Label_dependent_variable, Label_independent_variable);
+                    // document.getElementById('img_box_url').src = "";
 
+                } else if (chartType === "bar") {
+                    console.log('圖表類型bar')
+                    $('#textToChart').hide();
+                    extractAndGenerateChart(dataArray);
+                } else if (chartType === "doughnut") {
+                    $('#textToChart').hide();
 
-            dataArray = response.data.data;
+                    generatePieChart(dataArray)
+                } else {
+                    $('#chartdiv').hide();
+                    generateChart(dataArray);
+                }
+                // }
 
+                // 在這裡處理成功獲取 JSON 的情況
+                // console.log(response);
 
-            if (chartType === "table") {
-                createTable(dataArray, unit, Label_dependent_variable, Label_independent_variable);
-                // document.getElementById('img_box_url').src = "";
 
-            } else if (chartType === "bar") {
-                console.log('圖表類型bar')
-                $('#textToChart').hide();
-                extractAndGenerateChart(dataArray);
-            } else if (chartType === "doughnut") {
-                $('#textToChart').hide();
 
-                generatePieChart(dataArray)
-            } else {
-                $('#chartdiv').hide();
-                generateChart(dataArray);
+
+                // switch (chartType) {
+                //     case "line":
+                //         document.getElementById("inlineRadio4").checked = true; // 折线图
+                //         break;
+                //     case "bar":
+                //         document.getElementById("inlineRadio5").checked = true; // 柱状图
+                //         labelDependent = response.data.chart_info.Label_dependent_variable;
+                //         break;
+                //     case "pie":
+                //         document.getElementById("inlineRadio6").checked = true; // 圆饼图
+                //         break;
+                //     case "table":
+                //         document.getElementById("inlineRadio7").checked = true; // 表格
+                //         break;
+                //     default:
+                //     // 默认情况
             }
 
+
+
             // generateChart(dataArray);
         })
         .catch(error => {
@@ -1752,3 +1780,414 @@ document.querySelectorAll('input[name="ChartOptions"]').forEach(function (radio)
     });
 });
 
+
+
+function tokencheck() {
+    if (!localStorage.getItem("access_token")) {
+        window.location.href = "./login.html";
+    } else {
+        console.log("存在");
+    }
+}
+
+tokencheck();
+
+function logout() {
+    localStorage.removeItem("access_token");
+}
+
+$(document).on("click", "#logout", function (event) {
+    alert("登出成功");
+    logout();
+    location.reload();
+});
+
+
+// speech_to_text
+
+
+
+
+
+var closeRecord = document.getElementById('recording_block_close');
+var recording_block = document.getElementById('recording_block');
+
+var PageLM = "2024-05-07 18:33";
+
+var recording_button = document.getElementById('recording_button');
+
+var stopButton = document.getElementById('stop');
+
+var audio = document.getElementById('audio');
+
+var recorder, audioBlob;
+
+closeRecord.addEventListener('click', () => {
+    recPause();
+    console.log('record-click')
+    recording_block.classList.remove('show');
+    recording_block.classList.add('hidden');
+});
+
+
+
+function reclog(s, color) {
+    var now = new Date();
+    var t = ("0" + now.getHours()).substr(-2)
+        + ":" + ("0" + now.getMinutes()).substr(-2)
+        + ":" + ("0" + now.getSeconds()).substr(-2);
+    var div = document.createElement("div");
+    var elem = document.querySelector(".reclog");
+    elem.insertBefore(div, elem.firstChild);
+    div.innerHTML = '<div style="color:' + (!color ? "" : color == 1 ? "red" : color == 2 ? "#0b1" : color) + '">[' + t + ']' + s + '</div>';
+};
+window.onerror = function (message, url, lineNo, columnNo, error) {
+    //https://www.cnblogs.com/xianyulaodi/p/6201829.html
+    reclog('<span style="color:red">【Uncaught Error】' + message + '<pre>' + "at:" + lineNo + ":" + columnNo + " url:" + url + "\n" + (error && error.stack || Html_$T("kBaF::不能获得错误堆栈")) + '</pre></span>');
+};
+
+if (!window.Html_$T) {//没有提供本页面用的国际化多语言支持时 返回中文文本
+    window.Html_$T = function () {
+        var a = arguments, txt = a[0].replace(/^.+?::/, ""), n = 0;
+        for (var i = 0; i < a.length; i++) { if (typeof a[i] == "number") { n = i; break } }
+        txt = txt.replace(/\{(\d+)\}/g, function (v, f) { v = a[+f + n]; return v == null ? "" : v });
+        return txt;
+    }
+    window.Html_xT = function (v) { return v }
+}
+
+
+if (window.Recorder) {
+    // reclog(Html_$T('BL9u::頁面已準備好,請先點擊打開錄音,然後點擊錄製'), 2);
+    console.log('頁面已準備好,請先點擊打開錄音,然後點擊錄製')
+} else {
+    reclog(Html_$T("YzPd::js文件加载失败,请刷新重试!"), "#f00;font-size:50px");
+
+    console.log('js文件加载失敗,请刷新重试!')
+
+}
+
+recording_button.addEventListener('click', async () => {
+
+    recording_block.classList.toggle('show');
+    recording_block.classList.toggle('hidden');
+
+    recStart();
+
+
+});
+
+
+
+function recStart() {//打开了录音后才能进行start、stop调用
+    rec.start();
+};
+
+
+stopButton.addEventListener('click', async () => {
+    // recorder.stop();
+
+    // uploadAudio(audioBlob);
+
+    recStop();
+});
+
+
+window.onload = function () {
+    recOpen();
+};
+
+// 錄音程式
+// =======================
+var rec, wave, recBlob;
+/**调用open打开录音请求好录音权限  Call open to open the recording and request the recording permission**/
+var recOpen = function () {//一般在显示出录音按钮或相关的录音界面时进行此方法调用,后面用户点击开始录音时就能畅通无阻了
+
+    rec = null;
+    wave = null;
+    recBlob = null;
+    var newRec = Recorder({
+        type: "mp3", sampleRate: 16000, bitRate: 16 //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把格式支持文件加载进来,比如使用wav格式需要提前加载wav.js编码引擎
+        , onProcess: function (buffers, powerLevel, bufferDuration, bufferSampleRate, newBufferIdx, asyncEnd) {
+            // // //录音实时回调,大约1秒调用12次本回调
+            document.querySelector(".recpowerx").style.width = powerLevel + "%";
+            document.querySelector(".recpowert").innerText = formatMs(bufferDuration, 1) + " / " + powerLevel;
+
+            // //可视化图形绘制
+            // wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
+        }
+    });
+
+    newRec.open(function () {//打开麦克风授权获得相关资源
+        rec = newRec;
+
+        //此处创建这些音频可视化图形绘制浏览器支持妥妥的
+        // wave = Recorder.FrequencyHistogramView({ elem: ".recwave" });
+
+        reclog(Html_$T("GVCa::已打開錄音,可以點擊錄制開始錄音"), 2);
+    }, function (msg, isUserNotAllow) {//用户拒绝未授权或不支持
+        reclog((isUserNotAllow ? "UserNotAllow, " : "") + Html_$T("TOOV::打開錄音失敗:") + msg, 1);
+    });
+
+};
+
+
+
+
+/**关闭录音,释放资源  Close recording, release resources**/
+function recClose() {
+    if (rec) {
+        rec.close();
+        reclog(Html_$T("jqOs::已关闭"));
+    } else {
+        reclog(Html_$T("VOOw::未打开录音"), 1);
+    };
+};
+
+
+
+/**开始录音  Start recording**/
+function recStart() {//打开了录音后才能进行start、stop调用
+    if (rec && Recorder.IsOpen()) {
+        recBlob = null;
+        rec.start();
+        reclog(Html_$T("CGdy::已經開始錄音...") + " " + rec.set.type + " " + rec.set.sampleRate + " " + rec.set.bitRate + "kbps");
+    } else {
+        reclog(Html_$T("ajKR::未打开錄音"), 1);
+    };
+};
+
+/**暂停录音  Passing recording**/
+function recPause() {
+    if (rec && Recorder.IsOpen()) {
+        rec.pause();
+        reclog(Html_$T("GvCy::已暂停"));
+    } else {
+        reclog(Html_$T("gCAR::未打開錄音"), 1);
+    };
+};
+/**恢复录音  Resume recording**/
+function recResume() {
+    if (rec && Recorder.IsOpen()) {
+        rec.resume();
+        reclog(Html_$T("5q1K::继续录音中..."));
+    } else {
+        reclog(Html_$T("Ob6S::未打开录音"), 1);
+    };
+};
+
+/**结束录音,得到音频文件  Stop recording and get audio files**/
+function recStop() {
+    if (!(rec && Recorder.IsOpen())) {
+        reclog(Html_$T("5JuL::未打開錄音"), 1);
+        return;
+    };
+    rec.stop(function (blob, duration) {
+        console.log(blob, (window.URL || webkitURL).createObjectURL(blob), Html_xT(Html_$T("gOix::時長:{1}ms", 0, duration)));
+
+        recBlob = blob;
+        var file = new File([blob], "recording.mp3", { type: "audio/mp3" });
+        console.log(file);
+        handleAudioToText(file);
+        reclog(Html_$T("0LHf::已錄製mp3:{1}ms {2}字節,可以點擊播放、上傳、本地下载了", 0, formatMs(duration), blob.size), 2);
+    }, function (msg) {
+        reclog(Html_$T("kGZO::錄音失敗:") + msg, 1);
+    });
+};
+
+
+
+
+
+
+
+
+
+/**播放  Play**/
+function recPlay() {
+    if (!recBlob) {
+        reclog(Html_$T("tIke::请先錄音,然后停止後再播放"), 1);
+        return;
+    };
+    var cls = ("a" + Math.random()).replace(".", "");
+    reclog(Html_$T('GlWb::播放中: ') + '<span class="' + cls + '"></span>');
+    var audio = document.createElement("audio");
+    audio.controls = true;
+    document.querySelector("." + cls).appendChild(audio);
+    //简单利用URL生成播放地址,注意不用了时需要revokeObjectURL,否则霸占内存
+    audio.src = (window.URL || webkitURL).createObjectURL(recBlob);
+    audio.play();
+
+    setTimeout(function () {
+        (window.URL || webkitURL).revokeObjectURL(audio.src);
+    }, 5000);
+};
+
+/**上传  Upload**/
+function recUpload() {
+    var blob = recBlob;
+    if (!blob) {
+        reclog(Html_$T("DUTn::请先录音,然后停止后再上传"), 1);
+        return;
+    };
+
+    //本例子假设使用原始XMLHttpRequest请求方式,实际使用中自行调整为自己的请求方式
+    //录音结束时拿到了blob文件对象,可以用FileReader读取出内容,或者用FormData上传
+    var api = "http://127.0.0.1:9528";
+    var onreadystatechange = function (xhr, title) {
+        return function () {
+            if (xhr.readyState == 4) {
+                if (xhr.status == 200) {
+                    reclog(title + Html_$T("G2MU::上传成功") + ' <span style="color:#999">response: ' + xhr.responseText + '</span>', 2);
+                } else {
+                    reclog(title + Html_$T("TUdi::没有完成上传,演示上传地址无需关注上传结果,只要浏览器控制台内Network面板内看到的请求数据结构是预期的就ok了。"), "#d8c1a0");
+
+                    console.error(Html_xT(title + Html_$T("HjDi::上传失败")), xhr.status, xhr.responseText);
+                };
+            };
+        };
+    };
+    reclog(Html_$T("QnSI::开始上传到{1},请稍候... (你可以先到源码 /assets/node-localServer 目录内执行 npm run start 来运行本地测试服务器)", 0, api));
+
+    /***方式一:将blob文件转成base64纯文本编码,使用普通application/x-www-form-urlencoded表单上传***/
+    var reader = new FileReader();
+    reader.onloadend = function () {
+        var postData = "";
+        postData += "mime=" + encodeURIComponent(blob.type);//告诉后端,这个录音是什么格式的,可能前后端都固定的mp3可以不用写
+        postData += "&upfile_b64=" + encodeURIComponent((/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result) || [])[1]) //录音文件内容,后端进行base64解码成二进制
+        //...其他表单参数
+
+        var xhr = new XMLHttpRequest();
+        xhr.open("POST", api + "/uploadBase64");
+        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+        xhr.onreadystatechange = onreadystatechange(xhr, Html_$T("gG1f::上传方式一【Base64】"));
+        xhr.send(postData);
+    };
+    reader.readAsDataURL(blob);
+
+    /***方式二:使用FormData用multipart/form-data表单上传文件***/
+    var form = new FormData();
+    form.append("upfile", blob, "recorder.mp3"); //和普通form表单并无二致,后端接收到upfile参数的文件,文件名为recorder.mp3
+    //...其他表单参数
+
+    var xhr = new XMLHttpRequest();
+    xhr.open("POST", api + "/upload");
+    xhr.onreadystatechange = onreadystatechange(xhr, Html_$T("vDzB::上传方式二【FormData】"));
+    xhr.send(form);
+};
+
+
+/**本地下载  Local download**/
+function recLocalDown() {
+    if (!recBlob) {
+        reclog(Html_$T("M86h::请先录音,然后停止后再下载"), 1);
+        return;
+    };
+    var cls = ("a" + Math.random()).replace(".", "");
+    recdown64.lastCls = cls;
+    reclog(Html_$T('vJPl::点击 ') + '<span class="' + cls + '"></span>' + Html_$T('Whtc:: 下载,或复制文本')
+        + '<button onclick="recdown64(\'' + cls + '\')">' + Html_$T('XK4l::生成Base64文本') + '</button><span class="' + cls + '_b64"></span>');
+
+    var fileName = "recorder-" + Date.now() + ".mp3";
+    var downA = document.createElement("A");
+    downA.innerHTML = Html_$T("g8Fy::下载 ") + fileName;
+    downA.href = (window.URL || webkitURL).createObjectURL(recBlob);
+    downA.download = fileName;
+    document.querySelector("." + cls).appendChild(downA);
+    if (/mobile/i.test(navigator.userAgent)) {
+        alert(Html_xT(Html_$T("DIEK::因移动端绝大部分国产浏览器未适配Blob Url的下载,所以本demo代码在移动端未调用downA.click()。请尝试点击日志中显示的下载链接下载")));
+    } else {
+        downA.click();
+    }
+
+    //不用了时需要revokeObjectURL,否则霸占内存
+    //(window.URL||webkitURL).revokeObjectURL(downA.href);
+};
+function recdown64(cls) {
+    var el = document.querySelector("." + cls + "_b64");
+    if (recdown64.lastCls != cls) {
+        el.innerHTML = '<span style="color:red">' + Html_$T("eKKx::老的数据没有保存,只支持最新的一条") + '</span>';
+        return;
+    }
+    var reader = new FileReader();
+    reader.onloadend = function () {
+        el.innerHTML = '<textarea></textarea>';
+        el.querySelector("textarea").value = reader.result;
+    };
+    reader.readAsDataURL(recBlob);
+};
+
+
+
+
+
+
+
+
+
+var formatMs = function (ms, all) {
+    var ss = ms % 1000; ms = (ms - ss) / 1000;
+    var s = ms % 60; ms = (ms - s) / 60;
+    var m = ms % 60; ms = (ms - m) / 60;
+    var h = ms;
+    var t = (h ? h + ":" : "")
+        + (all || h + m ? ("0" + m).substr(-2) + ":" : "")
+        + (all || h + m + s ? ("0" + s).substr(-2) + "″" : "")
+        + ("00" + ss).substr(-3);
+    return t;
+};
+
+
+// =======================
+
+
+let mediaRecorder;
+let audioChunks = [];
+
+
+
+async function handleAudioToText(file) {
+    console.log('tts')
+    let audioLang = "cmn-Hant-TW"; // 音訊語言
+    // let lang = localStorage.getItem("lang");
+    let lang = "zh-tw";
+
+
+
+    console.log("lang", lang);
+
+    switch (lang) {
+        case "zh-tw":
+            audioLang = "cmn-Hant-TW";
+            break;
+        case "en-us":
+            audioLang = "en-US";
+            break;
+        case "ja-jp":
+            audioLang = "ja-JP";
+            break;
+        case "ko-kr":
+            audioLang = "ko-KR";
+            break;
+        default:
+            break;
+    }
+
+    let url = `https://bf18-61-230-0-215.ngrok-free.app/gcp/speech-to-text?language_code=${audioLang}`;
+
+    const formData = new FormData();
+    formData.append("file", file);
+
+    try {
+        const response = await axios.post(url, formData);
+        console.log("response", response);
+        input_text_value = response.data;
+        console.log(input_text_value[0]);
+        recording_block.classList.remove('show');
+        recording_block.classList.add('hidden');
+        get_data(input_text_value);
+
+    } catch (error) {
+        console.log("error", error);
+    }
+}

+ 59 - 0
js/login.js

@@ -0,0 +1,59 @@
+console.log('login');
+
+var login = document.getElementById("loginbutton");
+
+var data = [];
+
+var accestoken;
+
+login.addEventListener('click', function () {
+    let username = document.querySelector("#email").value;
+    let password = document.querySelector("#password").value;
+
+    var config = {
+        headers: {
+            'accept': 'application/json'
+        }
+    };
+
+    let data = new URLSearchParams();
+    data.append('username', username);
+    data.append('password', password);
+
+    axios
+        .post("https://cmm.ai:8080/auth/token", data, config)
+        .then((response) => {
+            console.log(response);
+
+
+            accestoken = response.data.access_token;
+
+            console.log(accestoken)
+
+            localStorage.setItem("access_token", accestoken);
+            localStorage.setItem("username", username);
+
+
+            alert('登入成功')
+
+            window.location.href = "./index.html";
+
+
+            //   history.go(0);
+        })
+        .catch((error) =>
+            alert('帳號或密碼錯誤')
+        );
+
+    // if (username === 'choozmo' && password === "choozmo9") {
+    //     alert('登入成功');
+    //     window.location.href = "./index.html";
+    // } else {
+    //     alert('帳號或密碼錯誤')
+    // }
+
+});
+
+
+
+

+ 49 - 0
js/register.js

@@ -0,0 +1,49 @@
+console.log('register');
+
+var register = document.getElementById("registerButton");
+
+register.addEventListener('click', function () {
+    let account = document.querySelector("#InputAccount").value;
+
+    let repassword = document.querySelector("#RepeatPassword").value;
+    let password = document.querySelector("#InputPassword").value;
+
+    var data = {
+        "username": account,
+        "password": password,
+    };
+
+    if (validateEmail(account)) {
+        console.log('email有效')
+
+        if (repassword != password) {
+            alert('密碼與確認密碼不相同')
+            return
+        }
+        axios
+            .post("https://cmm.ai:8080/auth/register", data)
+            .then((response) => {
+                console.log(response);
+                alert(response.data.msg)
+                //   history.go(0);
+            })
+            .catch((error) => console.log(error));
+
+    } else {
+        alert('請輸入有效的email')
+        return
+
+    }
+
+
+
+
+
+
+});
+
+
+function validateEmail(email) {
+    const re = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
+    return re.test(email);
+}

+ 23 - 14
login.html

@@ -44,11 +44,14 @@
                                     <form class="user">
                                         <div class="form-group">
                                             <input type="email" class="form-control form-control-user" id="email"
-                                                aria-describedby="emailHelp" placeholder="Enter account s...">
+                                                aria-describedby="emailHelp" placeholder="請輸入Email">
                                         </div>
                                         <div class="form-group">
                                             <input id="password" type="password" class="form-control form-control-user"
-                                                id="exampleInputPassword" placeholder="Password">
+                                                id="exampleInputPassword" placeholder="請輸入密碼">
+                                        </div>
+                                        <div class="text-center mb-3">
+                                            <a class="small" href="register.html">建立帳戶</a>
                                         </div>
                                         <!-- <div class="form-group">
                                             <div class="custom-control custom-checkbox small">
@@ -58,7 +61,7 @@
                                             </div>
                                         </div> -->
                                         <a id="loginbutton" class="btn btn-primary btn-user btn-block">
-                                            Login
+                                            登入
                                         </a>
                                         <!-- <hr> -->
                                         <!-- <a href="index.html" class="btn btn-google btn-user btn-block">
@@ -94,25 +97,31 @@
     <!-- Core plugin JavaScript-->
     <script src="vendor/jquery-easing/jquery.easing.min.js"></script>
 
+    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
+
+
     <!-- Custom scripts for all pages-->
     <script src="js/sb-admin-2.min.js"></script>
 
+    <script src="js/login.js"></script>
+
+
     <script>
-        var login = document.getElementById("loginbutton");
+        // var login = document.getElementById("loginbutton");
 
-        login.addEventListener('click', function () {
-            let username = document.querySelector("#email").value;
-            let password = document.querySelector("#password").value;
+        // login.addEventListener('click', function () {
+        //     let username = document.querySelector("#email").value;
+        //     let password = document.querySelector("#password").value;
 
-            if (username === 'choozmo' && password === "choozmo9") {
-                alert('登入成功');
-                window.location.href = "./index.html";
-            } else {
-                alert('帳號或密碼錯誤')
-            }
+        //     if (username === 'choozmo' && password === "choozmo9") {
+        //         alert('登入成功');
+        //         window.location.href = "./index.html";
+        //     } else {
+        //         alert('帳號或密碼錯誤')
+        //     }
 
 
-        });
+        // });
 
     </script>
 

+ 89 - 0
register.html

@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+    <meta name="description" content="">
+    <meta name="author" content="">
+
+    <title>Sanlih Entertainment Television</title>
+
+    <!-- Custom fonts for this template-->
+    <link href="vendor/fontawesome-free/css/all.min.css" rel="stylesheet" type="text/css">
+    <link
+        href="https://fonts.googleapis.com/css?family=Nunito:200,200i,300,300i,400,400i,600,600i,700,700i,800,800i,900,900i"
+        rel="stylesheet">
+
+    <!-- Custom styles for this template-->
+    <link href="css/sb-admin-2.min.css" rel="stylesheet">
+
+</head>
+
+<body style="background: #1c2d6d;" class="bg-gradient-primary">
+
+    <div class="container">
+
+        <div class="card o-hidden border-0 shadow-lg my-5">
+            <div class="card-body p-0">
+                <!-- Nested Row within Card Body -->
+                <div class="row">
+                    <div class="col-lg-12">
+                        <div class="p-5">
+                            <div class="text-center">
+                                <h1 class="h4 text-gray-900 mb-4">建立帳戶</h1>
+                            </div>
+                            <form class="user">
+                                <div class="form-group">
+                                    <input type="email" class="form-control form-control-user" id="InputAccount"
+                                        placeholder="請輸入email">
+                                </div>
+                                <div class="form-group row">
+                                    <div class="col-sm-6 mb-3 mb-sm-0">
+                                        <input type="password" class="form-control form-control-user" id="InputPassword"
+                                            placeholder="請輸入密碼">
+                                    </div>
+                                    <div class="col-sm-6">
+                                        <input type="password" class="form-control form-control-user"
+                                            id="RepeatPassword" placeholder="請再次輸入密碼">
+                                    </div>
+                                </div>
+                                <a id="registerButton" class="btn btn-primary btn-user btn-block">
+                                    建立帳戶
+                                </a>
+
+                            </form>
+                            <hr>
+                            <!-- <div class="text-center">
+                                <a class="small" href="forgot-password.html">Forgot Password?</a>
+                            </div> -->
+                            <div class="text-center">
+                                <a class="small" href="login.html">已有帳戶? 登入!</a>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+    </div>
+
+    <!-- Bootstrap core JavaScript-->
+    <script src="vendor/jquery/jquery.min.js"></script>
+    <script src="vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
+
+    <!-- Core plugin JavaScript-->
+    <script src="vendor/jquery-easing/jquery.easing.min.js"></script>
+
+    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
+
+    <!-- Custom scripts for all pages-->
+    <script src="js/sb-admin-2.min.js"></script>
+    <script src="js/register.js"></script>
+
+
+</body>
+
+</html>

+ 468 - 0
src/app-support/app-miniProgram-wx-support.js

@@ -0,0 +1,468 @@
+/*
+录音 RecordApp: 微信小程序支持文件,支持在微信小程序环境中使用
+https://github.com/xiangyuecn/Recorder
+
+录音功能由微信小程序的RecorderManager录音接口提供(已屏蔽10分钟录音限制),因为js层已加载Recorder和相应的js编码引擎,所以,Recorder支持的录音格式,小程序内均可以做到支持。
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var IsWx=typeof wx=="object" && !!wx.getRecorderManager;
+var App=Recorder.RecordApp;
+var CLog=App.CLog;
+
+var platform={
+	Support:function(call){
+		call(IsWx);
+	}
+	,CanProcess:function(){
+		return true;//支持实时回调
+	}
+};
+App.RegisterPlatform("miniProgram-wx",platform);
+
+
+
+//当使用到录音的页面onShow时进行一次调用,用于恢复被暂停的录音(比如按了home键会暂停录音)
+App.MiniProgramWx_onShow=function(){
+	recOnShow();
+};
+
+
+
+/*******实现统一接口*******/
+platform.RequestPermission=function(sid,success,fail){
+	requestPermission(success,fail);
+};
+platform.Start=function(sid,set,success,fail){
+	onRecFn.param=set;
+	var rec=Recorder(set);
+	rec.set.disableEnvInFix=true; //不要音频输入丢失补偿
+	rec.dataType="arraybuffer";
+	
+	onRecFn.rec=rec;//等待第一个数据到来再调用rec.start
+	App.__Rec=rec;//App需要暴露出使用到的rec实例
+	
+	recStart(success,fail);
+};
+platform.Stop=function(sid,success,fail){
+	clearCurMg();
+	
+	var failCall=function(msg){
+		if(App.__Sync(sid)){
+			onRecFn.rec=null;
+		}
+		fail(msg);
+	};
+	var rec=onRecFn.rec;
+	onRecFn.rec=null;
+	
+	var clearMsg=success?"":App.__StopOnlyClearMsg();
+	if(!rec){
+		failCall("未开始录音"+(clearMsg?" ("+clearMsg+")":""));
+		return;
+	};
+	
+	CLog("rec encode: pcm:"+rec.recSize+" srcSR:"+rec.srcSampleRate+" set:"+JSON.stringify(onRecFn.param));
+	
+	var end=function(){
+		if(App.__Sync(sid)){
+			//把可能变更的配置写回去
+			for(var k in rec.set){
+				onRecFn.param[k]=rec.set[k];
+			};
+		};
+	};
+	if(!success){
+		end();
+		failCall(clearMsg);
+		return;
+	};
+	rec.stop(function(arrBuf,duration,mime){
+		end();
+		success(arrBuf,duration,mime);
+	},function(msg){
+		end();
+		failCall(msg);
+	});
+};
+
+
+
+
+var onRecFn=function(pcm,sampleRate){
+	var rec=onRecFn.rec;
+	if(!rec){
+		CLog("未开始录音,但收到wx PCM数据",3);
+		return;
+	};
+	if(!rec._appStart){
+		rec.envStart({
+			envName:platform.Key,canProcess:platform.CanProcess()
+		},sampleRate);
+	};
+	rec._appStart=1;
+	
+	var sum=0;
+	for(var i=0;i<pcm.length;i++){
+		sum+=Math.abs(pcm[i]);
+	}
+	
+	rec.envIn(pcm,sum);
+};
+
+
+
+/*******微信小程序录音接口调用*******/
+var hasPermission=false;
+var requestPermission=function(success,fail){
+	clearCurMg();
+	initSys();
+	if(hasPermission){
+		success(); return;
+	}
+	var mg=wx.getRecorderManager(),next=1;
+	mg.onStart(function(){
+		hasPermission=true;
+		if(next){ next=0;
+			stopMg(mg);
+			success();
+		}
+	});
+	mg.onError(function(res){
+		var msg="请求录音权限出现错误:"+res.errMsg;
+		CLog(msg+"。"+UserPermissionMsg,1,res);
+		if(next){ next=0;
+			stopMg(mg);
+			fail(msg,true);
+		}
+	});
+	newStart("req",mg);
+};
+var UserPermissionMsg="请自行检查wx.getSetting中的scope.record录音权限,如果用户拒绝了权限,请引导用户到小程序设置中授予录音权限。";
+
+var curMg,mgStime=0;
+var clearCurMg=function(){
+	var old=curMg;curMg=null;
+	if(old){ stopMg(old) }
+};
+var stopMg=function(mg){
+	mgStime=Date.now();
+	mg.stop();
+};
+var newStart=function(tag,mg){ //统一参数进行start调用,不然开发工具上热更新参数不一样直接卡死
+	var obj={
+		duration:600000
+		,sampleRate:48000 //pc端无效
+		,encodeBitRate:320000
+		,numberOfChannels:1
+		,format:"PCM"
+		,frameSize:isDev?1:4 //4=48/12
+	};
+	var set=onRecFn.param||{},aec=(set.audioTrackSet||{}).echoCancellation;
+	if(sys.platform=="android"){ //Android指定麦克风源 MediaRecorder.AudioSource,0 DEFAULT 默认音频源,1 MIC 主麦克风,5 CAMCORDER 相机方向的麦,6 VOICE_RECOGNITION 语音识别,7 VOICE_COMMUNICATION 语音通信(带回声消除)
+		var source=set.android_audioSource,asVal="";
+		if(source==null && aec) source=7;
+		if(source==null) source=App.Default_Android_AudioSource;
+		if(source==1) asVal="mic";
+		if(source==5) asVal="camcorder";
+		if(source==6) asVal="voice_recognition";
+		if(source==7) asVal="voice_communication";
+		if(asVal)obj.audioSource=asVal;
+	};
+	if(aec){
+		CLog("mg注意:iOS下无法配置回声消除,Android无此问题,建议都启用听筒播放避免回声:wx.setInnerAudioOption({speakerOn:false})",3);
+	};
+	CLog("["+tag+"]mg.start obj",obj);
+	mg.start(obj);
+};
+var recOnShow=function(){
+	if(curMg && curMg.__pause){
+		CLog("mg onShow 录音开始恢复...",3);
+		curMg.resume();
+	}
+};
+var recStart=function(success,fail){
+	clearCurMg();
+	initSys();
+	devWebMInfo={};
+	if(isDev){
+		CLog("RecorderManager.onFrameRecorded 在开发工具中测试返回的是webm格式音频,将会尝试进行解码",3);
+	}
+	
+	var startIsEnd=false,startCount=1;
+	var startEnd=function(err){
+		if(startIsEnd)return; startIsEnd=true;
+		if(err){
+			clearCurMg();
+			fail(err);
+		}else{
+			success();
+		};
+	};
+	
+	var mg=curMg=wx.getRecorderManager();
+	mg.onInterruptionEnd(function(){
+		if(mg!=curMg)return;
+		CLog("mg onInterruptionEnd 录音开始恢复...",3);
+		mg.resume();
+	});
+	mg.onPause(function(){
+		if(mg!=curMg)return;
+		mg.__pause=Date.now();
+		CLog("mg onPause 录音被打断",3);
+	});
+	mg.onResume(function(){
+		if(mg!=curMg)return;
+		var t=mg.__pause?Date.now()-mg.__pause:0,t2=0;
+		mg.__pause=0;
+		if(t>300){//填充最多1秒的静默
+			t2=Math.min(1000,t);
+			onRecFn(new Int16Array(48000/1000*t2),48000);
+		}
+		CLog("mg onResume 恢复录音,填充了"+t2+"ms静默",3);
+	});
+	mg.onError(function(res){
+		if(mg!=curMg)return;
+		var msg=res.errMsg,tag="mg onError 开始录音出错:";
+		if(!startIsEnd && !mg._srt && /fail.+is.+recording/i.test(msg)){
+			var st=600-(Date.now()-mgStime); //距离上次停止未超过600毫秒,重试
+			if(st>0){ st=Math.max(100,st);
+				CLog(tag+"等待"+st+"ms重试",3,res);
+				setTimeout(function(){
+					if(mg!=curMg)return; mg._srt=1;
+					CLog(tag+"正在重试",3);
+					newStart("retry start",mg);
+				}, st);
+				return;
+			};
+		};
+		CLog(startCount>1?tag+"可能无法继续录音["+startCount+"]。"+msg
+			:tag+msg+"。"+UserPermissionMsg,1,res);
+		startEnd("开始录音出错:"+msg);
+	});
+	mg.onStart(function(){
+		if(mg!=curMg)return;
+		CLog("mg onStart 已开始录音");
+		mg._srt=0; //下次开始失败可以重试
+		mg._st=Date.now();
+		startEnd();
+	});
+	mg.onStop(function(res){
+		CLog("mg onStop res:",res);
+		if(mg!=curMg)return;
+		if(!mg._st || Date.now()-mg._st<600){ CLog("mg onStop但已忽略",3); return }
+		CLog("mg onStop 已停止录音,正在重新开始录音...");
+		startCount++;
+		mg._st=0;
+		newStart("restart",mg);
+	});
+	
+	var start0=function(){
+		mg.onFrameRecorded(function(res){
+			if(mg!=curMg)return;
+			if(!startIsEnd)CLog("mg onStart未触发,但收到了onFrameRecorded",3);
+			startEnd();
+			
+			var aBuf=res.frameBuffer;
+			if(!aBuf || !aBuf.byteLength){
+				return;
+			}
+			if(isDev){
+				devWebmDecode(new Uint8Array(aBuf));
+			}else{
+				onRecFn(new Int16Array(aBuf),48000);
+			};
+		});
+		newStart("start",mg);
+	};
+	
+	var st=600-(Date.now()-mgStime); //距离上次停止未超过600毫秒,等待一会,一般是第一次请求权限后立马开始录音造成的(录音参数不一样,不共享同一个mg)
+	if(st>0){ st=Math.max(100,st);
+		CLog("mg.start距stop太近需等待"+st+"ms",3);
+		setTimeout(function(){ if(mg!=curMg)return; start0(); }, st);
+	}else{
+		start0();
+	};
+};
+
+
+
+
+var isDev,sys;
+var initSys=function(){
+	if(sys)return;
+	sys=wx.getSystemInfoSync();
+	isDev=sys.platform=="devtools"?1:0;
+	if(isDev){
+		devWebCtx=wx.createWebAudioContext();
+	}
+};
+
+
+/****开发工具内录音返回的webm数据解码成pcm,方便测试****/
+var devWebCtx,devWebMInfo;
+//=======从WebM字节流中提取pcm数据=====
+var devWebmDecode=function(inBytes){
+	var scope=devWebMInfo;
+	if(!scope.pos){
+		scope.pos=[0]; scope.tracks={}; scope.bytes=[];
+	};
+	var tracks=scope.tracks, position=[scope.pos[0]];
+	var endPos=function(){ scope.pos[0]=position[0] };
+	
+	var sBL=scope.bytes.length;
+	var bytes=new Uint8Array(sBL+inBytes.length);
+	bytes.set(scope.bytes); bytes.set(inBytes,sBL);
+	scope.bytes=bytes;
+	
+	//检测到不是webm,当做pcm直接返回
+	var returnPCM=function(){
+		scope.bytes=[];
+		onRecFn(new Int16Array(bytes),48000);
+	};
+	if(scope.isNotWebM){
+		returnPCM(); return;
+	};
+	
+	//先读取文件头和Track信息
+	if(!scope._ht){
+		//暴力搜索EBML Header,开头数据可能存在上次录音结尾数据
+		var headPos0=0;
+		for(var i=0;i<bytes.length;i++){
+			if(bytes[i]==0x1A && bytes[i+1]==0x45 && bytes[i+2]==0xDF && bytes[i+3]==0xA3){
+				headPos0=i;
+				position[0]=i+4; break;
+			}
+		}
+		if(!position[0]){
+			if(bytes.length>5*1024){
+				CLog("未识别到WebM数据,开发工具可能已支持PCM",3);
+				scope.isNotWebM=true;
+				returnPCM();
+			};
+			return;//未识别到EBML Header
+		}
+		readMatroskaBlock(bytes, position);//跳过EBML Header内容
+		if(!BytesEq(readMatroskaVInt(bytes, position), [0x18,0x53,0x80,0x67])){
+			return;//未识别到Segment
+		}
+		readMatroskaVInt(bytes, position);//跳过Segment长度值
+		while(position[0]<bytes.length){
+			var eid0=readMatroskaVInt(bytes, position);
+			var bytes0=readMatroskaBlock(bytes, position);
+			var pos0=[0],audioIdx=0;
+			if(!bytes0)return;//数据不全,等待缓冲
+			//Track完整数据,循环读取TrackEntry
+			if(BytesEq(eid0, [0x16,0x54,0xAE,0x6B])){
+				scope._ht=bytes.slice(headPos0,position[0]);
+				CLog("WebM Tracks",tracks);
+				endPos();
+				break;
+			}
+		}
+	}
+	
+	//循环读取Cluster内的SimpleBlock
+	var datas=[],dataLen=0;
+	while(position[0]<bytes.length){
+		var p0=position[0];
+		var eid1=readMatroskaVInt(bytes, position);
+		var p1=position[0];
+		var bytes1=readMatroskaBlock(bytes, position);
+		if(!bytes1)break;//数据不全,等待缓冲
+		if(BytesEq(eid1, [0xA3])){//SimpleBlock完整数据
+			var arr=bytes.slice(p0,position[0]);
+			dataLen+=arr.length;
+			datas.push(arr);
+		}
+		endPos();
+	}
+	
+	if(!dataLen){
+		return;
+	}
+	var more=new Uint8Array(bytes.length-scope.pos[0]);
+	more.set(bytes.subarray(scope.pos[0]));
+	scope.bytes=more; //清理已读取了的缓冲数据
+	scope.pos[0]=0;
+	
+	//和头一起拼接成新的webm
+	var add=[0x1F,0x43,0xB6,0x75,0x01,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF];//Cluster
+	add.push(0xE7,0x81,0x00);
+	dataLen+=add.length;
+	datas.splice(0,0,add);
+	
+	dataLen+=scope._ht.length;
+	datas.splice(0,0,scope._ht);
+	
+	var u8arr=new Uint8Array(dataLen); //已获取的音频数据
+	for(var i=0,i2=0;i<datas.length;i++){
+		u8arr.set(datas[i],i2);
+		i2+=datas[i].length;
+	}
+	
+	devWebCtx.decodeAudioData(u8arr.buffer, function(raw){
+		var src=raw.getChannelData(0);
+		var pcm=new Int16Array(src.length);
+		for(var i=0;i<src.length;i++){
+			var s=Math.max(-1,Math.min(1,src[i]));
+			s=s<0?s*0x8000:s*0x7FFF;
+			pcm[i]=s;
+		};
+		onRecFn(pcm,raw.sampleRate);
+	},function(){
+		CLog("WebM解码失败",1);
+	});
+};
+//两个字节数组内容是否相同
+var BytesEq=function(bytes1,bytes2){
+	if(!bytes1 || bytes1.length!=bytes2.length) return false;
+	if(bytes1.length==1) return bytes1[0]==bytes2[0];
+	for(var i=0;i<bytes1.length;i++){
+		if(bytes1[i]!=bytes2[i]) return false;
+	}
+	return true;
+};
+//字节数组BE转成int数字
+var BytesInt=function(bytes){
+	var s="";//0-8字节,js位运算只支持4字节
+	for(var i=0;i<bytes.length;i++){var n=bytes[i];s+=(n<16?"0":"")+n.toString(16)};
+	return parseInt(s,16)||0;
+};
+//读取一个可变长数值字节数组
+var readMatroskaVInt=function(arr,pos,trim){
+	var i=pos[0];
+	if(i>=arr.length)return;
+	var b0=arr[i],b2=("0000000"+b0.toString(2)).substr(-8);
+	var m=/^(0*1)(\d*)$/.exec(b2);
+	if(!m)return;
+	var len=m[1].length, val=[];
+	if(i+len>arr.length)return;
+	for(var i2=0;i2<len;i2++){ val[i2]=arr[i]; i++; }
+	if(trim) val[0]=parseInt(m[2]||'0',2);
+	pos[0]=i;
+	return val;
+};
+//读取一个自带长度的内容字节数组
+var readMatroskaBlock=function(arr,pos){
+	var lenVal=readMatroskaVInt(arr,pos,1);
+	if(!lenVal)return;
+	var len=BytesInt(lenVal);
+	var i=pos[0], val=[];
+	if(len<0x7FFFFFFF){ //超大值代表没有长度
+		if(i+len>arr.length)return;
+		for(var i2=0;i2<len;i2++){ val[i2]=arr[i]; i++; }
+	}
+	pos[0]=i;
+	return val;
+};
+//=====End WebM读取=====
+
+
+}));

+ 209 - 0
src/app-support/app-native-support.js

@@ -0,0 +1,209 @@
+/*
+录音 RecordApp: App Native支持文件,支持在浏览器环境中使用(Hybrid App)、各种适配后的js运行环境中使用(非浏览器环境)
+https://github.com/xiangyuecn/Recorder
+
+特别注明:本文件涉及的功能需要iOS、Android等App端提供的原生支持,如果你不能修改App的源码,并且坚决要使用本文件,那将会很困难。
+
+如果是在App内置的浏览器中进行录音(Hybrid App),应当首选使用Recorder H5进行录音;RecordApp+Native也可以在非浏览器环境中使用,比如:只有js运行时的app环境、nodejs环境。
+
+录音功能由原生App(Native)代码实现,通过JsBridge和js进行交互。Native层需要提供:请求权限、开始录音、结束录音、定时回调PCM[Int16]片段 等功能和接口。因为js层已加载Recorder和相应的js编码引擎,所以,Native层无需进行编码,可大大简化App的逻辑。
+
+录音必须是单声道的,因为这个库从头到尾就没有打算支持双声道。
+
+JsBridge可以是自己实现的交互方式 或 别人提供的框架。因为不知道具体使用的桥接方式,对应的请求已抽象成了4个方法在Native.Config中,需自行实现。
+
+注意:此文件并非拿来就能用的,需要改动【需实现】标注的地方;也可以不改动此文件,使用另外的初始化配置文件来进行配置,可参考app-support-sample目录内的配置文件,另外这个目录内还有Android和iOS的demo项目,copy源码改改就能用。
+
+如果是App内置的浏览器中使用时(H5),支持在iframe中使用,但如果是跨域要特殊处理。
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var App=Recorder.RecordApp;
+var CLog=App.CLog;
+
+var platform={
+	Support:function(call){
+		if(!App.AlwaysAppUseH5){
+			config.IsApp(call);
+			return;
+		};
+		//不支持app原生录音
+		call(false);
+	}
+	,CanProcess:function(){
+		return true;//支持实时回调
+	}
+	,Config:{
+		IsApp:function(call){
+			//如需打开原生App支持,此方法【需实现】,此方法用来判断:1. 判断app是否是在环境中 2. app支持录音
+			NeedConfigMsg("IsApp");
+			call(false);//默认实现不支持app原生录音,支持就回调call(true)
+		}
+		,JsBridgeRequestPermission:function(success,fail){
+			/*如需打开原生App支持,此方法【需实现】
+				success:fn() 有权限时回调
+				fail:fn(errMsg,isUserNotAllow) 出错回调
+			*/
+			fail(NeedConfigMsg("JsBridgeRequestPermission"));
+		}
+		,JsBridgeStart:function(set,success,fail){
+			/*如需打开原生App支持,此方法【需实现】,app打开录音后原生层定时返回PCM数据时JsBridge js层需要回调set.onProcess。建议JsBridge增加一个Alive接口,为录音时定时心跳请求,如果网页超过10秒未调用此接口,app原生层自动停止录音,防止stop不能调用导致的资源泄露。
+				set:RecordApp.Start的set参数
+				success:fn() 打开录音时回调
+				fail:fn(errMsg) 开启录音出错时回调
+			*/
+			fail(NeedConfigMsg("JsBridgeStart"));
+		}
+		,JsBridgeStop:function(success,fail){
+			/*如需打开原生App支持,此方法【需实现】
+				success:fn() 结束录音时回调
+				fail:fn(errMsg) 结束录音出错时回调
+			*/
+			fail(NeedConfigMsg("JsBridgeStop"));
+		}
+	}
+};
+App.RegisterPlatform("Native",platform);
+var config=platform.Config;
+
+var NeedConfigMsg=function(fn){
+	var msg=$T("WWoj::{1}中的{2}方法未实现,请在{3}文件中或配置文件中实现此方法",0,"RecordApp.Platforms.Native.Config",fn,"app-native-support.js");
+	CLog(msg,3);
+	return msg;
+};
+
+
+/*******App Native层在录音时定时回调本js方法*******/
+/*
+pcmDataBase64: base64<Int16[]>字符串 当前单声道录音缓冲PCM片段,正常情况下为上次回调本接口开始到现在的录音数据,Int16[]二进制数组需要编码成base64字符串;或者直接传一个Int16Array对象
+sampleRate:123456 录制音频实际的采样率
+*/
+var onRecFn=function(pcmDataBase64,sampleRate){
+	var rec=onRecFn.rec;
+	if(!rec){
+		CLog($T("rCAM::未开始录音,但收到Native PCM数据"),3);
+		return;
+	};
+	if(!rec._appStart){
+		rec.envStart({
+			envName:platform.Key,canProcess:platform.CanProcess()
+		},sampleRate);
+	};
+	rec._appStart=1;
+	
+	var sum=0;
+	if(pcmDataBase64 instanceof Int16Array){
+		var pcm=new Int16Array(pcmDataBase64);
+		for(var i=0;i<pcm.length;i++){
+			sum+=Math.abs(pcm[i]);
+		}
+	}else{
+		var bstr=atob(pcmDataBase64),n=bstr.length;
+		var pcm=new Int16Array(n/2);
+		for(var idx=0,s,i=0;i+2<=n;idx++,i+=2){
+			s=((bstr.charCodeAt(i)|(bstr.charCodeAt(i+1)<<8))<<16)>>16;
+			pcm[idx]=s;
+			sum+=Math.abs(s);
+		};
+	}
+	
+	rec.envIn(pcm,sum);
+};
+if(!isBrowser){
+	App.NativeRecordReceivePCM=onRecFn;
+};
+//尝试注入顶层window,用于接收Native回调数据,此处特殊处理一下,省得跨域的iframe无权限
+if(isBrowser){
+	window.NativeRecordReceivePCM=onRecFn;
+	try{
+		window.top.NativeRecordReceivePCM=onRecFn;
+	}catch(e){
+		var tipsFn=function(){
+			CLog($T("t2OF::检测到跨域iframe,NativeRecordReceivePCM无法注入到顶层,已监听postMessage转发兼容传输数据,请自行实现将top层接收到数据转发到本iframe(不限层),不然无法接收到录音数据"),3);
+		};
+		setTimeout(tipsFn,8000);
+		tipsFn();
+		
+		addEventListener("message",function(e){//纯天然,无需考虑origin
+			var data=e.data;//{type:"",data:{pcmDataBase64:"",sampleRate:16000}}
+			if(data&&data.type=="NativeRecordReceivePCM"){
+				data=data.data;
+				onRecFn(data.pcmDataBase64, data.sampleRate);
+			};
+		});
+	};
+};
+
+
+
+
+
+/*******实现统一接口*******/
+platform.RequestPermission=function(sid,success,fail){
+	config.JsBridgeRequestPermission(success,fail);
+};
+platform.Start=function(sid,set,success,fail){
+	onRecFn.param=set;
+	var rec=Recorder(set);
+	rec.set.disableEnvInFix=true; //不要音频输入丢失补偿
+	rec.dataType="arraybuffer";
+	
+	onRecFn.rec=rec;//等待第一个数据到来再调用rec.start
+	App.__Rec=rec;//App需要暴露出使用到的rec实例
+	
+	config.JsBridgeStart(set,success,fail);
+};
+platform.Stop=function(sid,success,fail){
+	var failCall=function(msg){
+		if(App.__Sync(sid)){
+			onRecFn.rec=null;
+		}
+		fail(msg);
+	};
+	config.JsBridgeStop(function(){
+		if(!App.__Sync(sid)){
+			failCall("Incorrect sync status");
+			return;
+		};
+		var rec=onRecFn.rec;
+		onRecFn.rec=null;
+		
+		var clearMsg=success?"":App.__StopOnlyClearMsg();
+		if(!rec){
+			failCall($T("Z2y2::未开始录音")
+				+(clearMsg?" ("+clearMsg+")":""));
+			return;
+		};
+		
+		CLog("rec encode: pcm:"+rec.recSize+" srcSR:"+rec.srcSampleRate+" set:"+JSON.stringify(onRecFn.param));
+		
+		var end=function(){
+			if(App.__Sync(sid)){
+				//把可能变更的配置写回去
+				for(var k in rec.set){
+					onRecFn.param[k]=rec.set[k];
+				};
+			};
+		};
+		if(!success){
+			end();
+			failCall(clearMsg);
+			return;
+		};
+		rec.stop(function(arrBuf,duration,mime){
+			end();
+			success(arrBuf,duration,mime);
+		},function(msg){
+			end();
+			failCall(msg);
+		});
+	},failCall);
+};
+
+}));

+ 450 - 0
src/app-support/app.js

@@ -0,0 +1,450 @@
+/*
+RecordApp:基于Recorder的跨平台录音,支持在浏览器环境中使用(H5)、各种使用js来构建的程序中使用(App、小程序、UniApp、Electron、NodeJs)
+https://github.com/xiangyuecn/Recorder
+
+示例demo请参考根目录内的app-support-sample目录
+
+使用时需先引入recorder-core和需要的编码器,再引入本js,再根据不同平台引入相应的app-xxx-support.js支持文件;如果引入的支持文件需要进行额外的配置,可参考app-support-sample目录内对应的配置文件。
+
+可以仅使用RecordApp作为入口,可以不关心recorder-core中的方法,因为RecordApp已经对他进行了一次封装,并且屏蔽了非通用的功能。
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(win,rec,ni,ni.$T,browser);
+	//umd returnExports.js
+	if(typeof(define)=='function' && define.amd){
+		define(function(){
+			return win.RecordApp;
+		});
+	};
+	if(typeof(module)=='object' && module.exports){
+		module.exports=win.RecordApp;
+	};
+}(function(Export,Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var App={
+LM:"2024-04-09 19:22"
+
+//当前使用的平台实现
+,Current:0
+//已注册的平台实现
+,Platforms:{} 
+};
+var Platforms=App.Platforms;
+var AppTxt="RecordApp";
+var ReqTxt="RequestPermission";
+var RegTxt="RegisterPlatform";
+
+var WApp2=Export[AppTxt];//重复加载js
+if(WApp2&&WApp2.LM==App.LM){
+	WApp2.CLog($T("uXtA::重复导入{1}",0,AppTxt),3);
+	return;
+};
+Export[AppTxt]=App;
+Recorder[AppTxt]=App;
+
+
+App.__SID_=0;//同步操作,防止同时多次调用
+var SID=App.__SID=function(){ return ++App.__SID_; };
+var Sync=App.__Sync=function(sid,tag,err){
+	if(App.__SID_!=sid){
+		if(tag){
+			CLog($T("kIBu::注意:因为并发调用了其他录音相关方法,当前 {1} 的调用结果已被丢弃且不会有回调",0,tag)+(err?", error: "+err:""),3);
+		}
+		return false;
+	}
+	return true;
+};
+
+var CLog=function(){
+	var v=arguments; v[0]="["+(CLog.Tag||AppTxt)+"]["+(App.Current&&App.Current.Key||"?")+"]"+v[0];
+	Recorder.CLog.apply(null,v);
+};
+App.CLog=CLog;
+
+
+/**
+注册一个平台的实现,对应的都会有一个app-xxx-support.js支持文件(Default-H5除外),config中提供统一的实现接口:
+{
+	Support: fn( call(canUse) ) 判断此平台是否支持或开启,如果平台可用需回调call(true)选择使用这个平台,并忽略其他平台
+	CanProcess: fn() 此平台是否支持实时回调,返回true代表支持
+	
+	Install: fn( success(),fail(errMsg) ) 可选,平台初始化安装,当使用此平台时会执行一次本方法(同一时间只会有一次调用,没有并发调用问题)
+	Pause: fn() 可选,暂停录音实现,如果返回false将执行默认暂停操作
+	Resume: fn() 可选,继续录音实现,如果返回false将执行默认继续操作
+	
+下面的方法中sid用于同步操作,在异步回调中用App.__Sync判断此sid是否处于同步状态
+实现中使用到的Recorder实例需赋值给App.__Rec(Stop结束后会自动清理并赋值为null)
+	
+	RequestPermission:fn(sid,success,fail) 实现录音权限请求,通过回调函数返回结果
+		success:fn() 有权限时回调
+		fail:fn(errMsg,isUserNotAllow) 没有权限或者不能录音时回调,如果是用户主动拒绝的录音权限,除了有错误消息外,isUserNotAllow=true,方便程序中做不同的提示,提升用户主动授权概率
+	
+	Start:fn(sid,set,success,fail) 实现开始录音
+		set:{} 和Recorder的set大部分参数相同
+		success:fn() 打开录音时回调
+		fail:fn(errMsg) 开启录音出错时回调
+	Start_Check:fn(set) 可选,调用本实现的Start前执行环境检查,返回检查错误文本,如果返回false将执行默认检查操作
+	AllStart_Clean:fn(set) 可选,任何实现的Start前执行本配置清理,set里面可能为了兼容不同平台环境会传入额外的参数,可以进行清理,无返回值
+	
+	Stop:fn(sid,success,fail) 实现结束录音,返回结果,success参数=null时仅清理资源
+		success:fn(arrayBuffer,duration,mime)	成功完成录音回调,参数可能为null
+			arrayBuffer:ArrayBuffer 录音数据
+			duration:123 //录音数据持续时间
+			mime:"audio/mp3" 录音数据格式
+		fail:fn(errMsg) 录音出错时回调
+}
+**/
+App[RegTxt]=function(key,config){ //App.RegisterPlatform=function()
+	config.Key=key;
+	if(Platforms[key]){
+		CLog($T("ha2K::重复注册{1}",0,key),3);
+	}
+	Platforms[key]=config;
+};
+App.__StopOnlyClearMsg=function(){
+	return $T("wpTL::仅清理资源");
+};
+
+/****实现默认的H5统一接口*****/
+var KeyH5="Default-H5",H5OpenSet=ReqTxt+"_H5OpenSet";
+(function(){
+var impl={
+	Support:function(call){
+		//默认的始终要支持
+		call(true);
+	}
+	,CanProcess:function(){
+		return true;//支持实时回调
+	}
+};
+App[RegTxt](KeyH5,impl);
+
+impl[ReqTxt]=function(sid,success,fail){ //impl.RequestPermission=function()
+	var old=App.__Rec;
+	if(old){
+		old.close();
+		App.__Rec=null;
+	};
+	h5Kill();
+	
+	//h5会提前打开录音,open时需要的配置只能单独配置
+	var h5Set=App[H5OpenSet]; App[H5OpenSet]=null;
+	
+	var rec=Recorder(h5Set||{});
+	rec.open(function(){
+		//rec.close(); keep stream Stop时再关,免得Start再次请求权限
+		success();
+	},fail);
+};
+var h5Kill=function(){ //释放检测权限时已打开的录音
+	if(Recorder.IsOpen()){
+		CLog("kill open...");
+		var rec=Recorder();
+		rec.open();
+		rec.close();
+	};
+};
+impl.Start=function(sid,set,success,fail){
+	var appRec=App.__Rec;
+	if(appRec!=null){
+		appRec.stop();//未stop的stop掉
+	};
+	App.__Rec=appRec=Recorder(set);
+	appRec.appSet=set;
+	appRec.dataType="arraybuffer";
+	appRec.open(function(){
+		if(Sync(sid)){
+			appRec.start();
+		};
+		success();
+	},fail);
+};
+impl.Stop=function(sid,success,fail){
+	var appRec=App.__Rec;
+	var clearMsg=success?"":App.__StopOnlyClearMsg();
+	if(!appRec){
+		h5Kill(); //释放检测权限时已打开的录音
+		fail($T("bpvP::未开始录音")
+			+(clearMsg?" ("+clearMsg+")":""));
+		return;
+	};
+	var end=function(){
+		if(Sync(sid)){
+			appRec.close();
+			//把可能变更的配置写回去
+			for(var k in appRec.set){
+				appRec.appSet[k]=appRec.set[k];
+			};
+		};
+	};
+	
+	var stopFail=function(msg){
+		end();
+		fail(msg);
+	};
+	if(!success){
+		stopFail(clearMsg);
+		return;
+	};
+	appRec.stop(function(arrBuf,duration,mime){
+		end();
+		success(arrBuf,duration,mime);
+	},stopFail);
+};
+
+})();
+
+
+
+
+
+/***
+获取底层平台录音过程中会使用用来处理实时数据的Recorder对象实例rec,如果底层录音过程中不使用Recorder进行数据的实时处理(目前没有),将返回null。Start调用前和Stop调用后均会返回null。
+
+rec中的方法不一定都能使用,主要用来获取内部缓冲用的,比如实时清理缓冲。
+***/
+App.GetCurrentRecOrNull=function(){
+	return App.__Rec||null;
+};
+
+/**暂停录音**/
+App.Pause=function(){
+	var cur=App.Current,key="Pause";
+	if(cur&&cur[key]){
+		if(cur[key]()!==false)return;
+	}
+	var rec=App.__Rec;
+	if(rec && canProc(key)){
+		rec.pause();
+	}
+};
+/**恢复录音**/
+App.Resume=function(){
+	var cur=App.Current,key="Resume";
+	if(cur&&cur[key]){
+		if(cur[key]()!==false)return;
+	}
+	var rec=App.__Rec;
+	if(rec && canProc(key)){
+		rec.resume();
+	}
+};
+var canProc=function(tag){
+	var cur=App.Current;
+	if(cur&&cur.CanProcess()) return 1;
+	CLog($T("fLJD::当前环境不支持实时回调,无法进行{1}",0,tag),3);
+};
+
+
+/***
+初始化安装,可反复调用
+success: fn() 初始化成功
+fail: fn(msg) 初始化失败
+***/
+App.Install=function(success,fail){
+	var cur=App.Current;
+	if(cur){ success&&success(); return; }
+	//因为此操作是异步的,为了避免竞争Current资源,此代码保证得到结果前只会发起一次调用
+	var reqs=App.__reqs||(App.__reqs=[]);
+	reqs.push({s:success,f:fail});
+	success=function(){ call("s",arguments) };
+	fail=function(){ call("f",arguments) };
+	var call=function(fn,args){
+		var arr=[].concat(reqs); reqs.length=0;
+		for(var i=0;i<arr.length;i++){
+			var f=arr[i][fn];
+			f&&f.apply(null,args);
+		};
+	};
+	if(reqs.length>1) return;
+	
+	var keys=[KeyH5],key;
+	for(var k in Platforms){
+		if(k!=KeyH5)keys.push(k);
+	}
+	keys.reverse();
+	var initCur=function(idx){
+		key=keys[idx];
+		cur=Platforms[key];
+		cur.Support(function(canUse){
+			if(!canUse){
+				return initCur(idx+1);
+			};
+			if(cur.Install){
+				cur.Install(initOk,fail);
+			}else{
+				initOk();
+			};
+		});
+	};
+	var initOk=function(){
+		App.Current=cur;
+		CLog("Install platform: "+key);
+		success();
+	};
+	initCur(0);
+};
+
+
+/***
+请求录音权限,如果当前环境不支持录音或用户拒绝将调用错误回调,调用start前需先至少调用一次此方法;请求权限后如果不使用了,不管有没有调用Start,至少要调用一次Stop来清理可能持有的资源。
+success:fn() 有权限时回调
+fail:fn(errMsg,isUserNotAllow) 没有权限或者不能录音时回调,如果是用户主动拒绝的录音权限,除了有错误消息外,isUserNotAllow=true,方便程序中做不同的提示,提升用户主动授权概率
+***/
+App[ReqTxt]=function(success,fail){ //App.RequestPermission=function(success,fail){
+	var sid=SID(),tag=AppTxt+"."+ReqTxt;
+	var failCall=function(errMsg,isUserNotAllow){
+		isUserNotAllow=!!isUserNotAllow;
+		var msg=errMsg+", isUserNotAllow:"+isUserNotAllow;
+		if(!Sync(sid,tag,msg))return;
+		CLog($T("YnzX::录音权限请求失败:")+msg,1);
+		fail&&fail(errMsg,isUserNotAllow);
+	};
+	CLog(ReqTxt+"...");
+	App.Install(function(){
+		if(!Sync(sid,tag))return;
+		
+		var checkMsg=CheckH5();
+		if(checkMsg){ failCall(checkMsg); return; };
+		
+		App.Current[ReqTxt](sid,function(){ //App.Current.RequestPermission()
+			if(!Sync(sid,tag))return;
+			CLog(ReqTxt+" Success");
+			success&&success();
+		},failCall);
+	},failCall);
+};
+var NeedReqMsg=function(){
+	return $T("nwKR::需先调用{1}",0,ReqTxt);
+};
+var CheckH5=function(){
+	var msg="";
+	if(App.Current.Key==KeyH5 && !isBrowser){
+		msg=$T("citA::当前不是浏览器环境,需引入针对此平台的支持文件({1}),或调用{2}自行实现接入",0,"src/app-support/app-xxx-support.js",AppTxt+"."+RegTxt);
+	};
+	return msg;
+};
+
+
+/***
+开始录音,需先调用RequestPermission获得录音权限
+
+set:设置默认值:{
+	type:"mp3"//最佳输出格式,如果底层实现能够支持就应当优先返回此格式
+	sampleRate:16000//最佳采样率hz
+	bitRate:16//最佳比特率kbps
+	onProcess:NOOP //如果当前环境支持实时回调(RecordApp.Current.CanProcess()),接收到录音数据时的回调函数:fn(buffers,powerLevel,bufferDuration,bufferSampleRate)
+	takeoffEncodeChunk:NOOP //fn(chunkBytes) 
+} 注意:此对象会被修改,因为平台实现时需要把实际使用的值存入此对象
+success:fn() 打开录音时回调
+fail:fn(errMsg) 开启录音出错时回调
+***/
+App.Start=function(set,success,fail){
+	var sid=SID(),tag=AppTxt+".Start";
+	var failCall=function(msg){
+		if(!Sync(sid,tag,msg))return;
+		CLog($T("ecp9::开始录音失败:")+msg,1);
+		fail&&fail(msg);
+	};
+	CLog("Start...");
+	
+	var cur=App.Current;
+	if(!cur){
+		failCall(NeedReqMsg());
+		return;
+	};
+	
+	set||(set={});
+	var obj={
+		type:"mp3"
+		,sampleRate:16000
+		,bitRate:16
+		,onProcess:function(){}
+	};
+	for(var k in obj){
+		set[k]||(set[k]=obj[k]);
+	};
+	
+	//对配置项进行清理,set里面可能为了兼容不同平台环境会传入额外的参数,可以进行清理
+	for(var k in Platforms){
+		var pf=Platforms[k];
+		if(pf.AllStart_Clean){
+			pf.AllStart_Clean(set);
+		}
+	};
+	
+	//先执行一下环境配置检查
+	var checkMsg=false;
+	if(cur.Start_Check){
+		checkMsg=cur.Start_Check(set);
+	};
+	if(checkMsg===false){
+		var checkRec=Recorder(set);
+		checkMsg=checkRec.envCheck({envName:cur.Key,canProcess:cur.CanProcess()});
+		if(!checkMsg) checkMsg=CheckH5();
+	}
+	if(checkMsg){
+		failCall($T("EKmS::不能录音:")+checkMsg);
+		return;
+	};
+	//重置Stop时的rec
+	App._SRec=0;
+	
+	cur.Start(sid,set,function(){
+		if(!Sync(sid,tag))return;
+		CLog($T("k7Qo::已开始录音"),set);
+		App._STime=Date.now();
+		success&&success();
+	},failCall);
+};
+
+
+/***
+结束录音
+
+success:fn(arrayBuffer,duration,mime)	结束录音时回调
+	arrayBuffer:ArrayBuffer 录音二进制数据
+	duration:123 录音时长,单位毫秒
+	mime:"auido/mp3" 录音格式类型
+	
+fail:fn(errMsg) 录音出错时回调
+
+本方法可以用来清理持有的资源,如果不提供success参数=null时,将不会进行音频编码操作,只进行清理完可能持有的资源后走fail回调
+***/
+App.Stop=function(success,fail){
+	var sid=SID(),tag=AppTxt+".Stop";
+	var failCall=function(msg){
+		if(!Sync(sid,tag,msg))return;
+		CLog($T("Douz::结束录音失败:")+msg,success?1:0);
+		try{
+			fail&&fail(msg);
+		}finally{ clear() }
+	};
+	var clear=function(){
+		App._SRec=App.__Rec;
+		App.__Rec=null;
+	};
+	CLog("Stop... "+$T("wqSH::和Start时差:{1}ms",0,App._STime?Date.now()-App._STime:-1)+" Recorder.LM:"+Recorder.LM+" "+AppTxt+".LM:"+App.LM);
+	var t1=Date.now();
+
+	var cur=App.Current;
+	if(!cur){
+		failCall(NeedReqMsg());
+		return;
+	};
+	
+	cur.Stop(sid, !success?null:function(arrayBuffer,duration,mime){
+		if(!Sync(sid,tag))return;
+		CLog($T("g3VX::结束录音 耗时{1}ms 音频时长{2}ms 文件大小{3}b {4}",0,Date.now()-t1,duration,arrayBuffer.byteLength,mime));
+		try{
+			success(arrayBuffer,duration,mime);
+		}finally{ clear() }
+	},failCall);
+};
+
+
+}));

Dosya farkı çok büyük olduğundan ihmal edildi
+ 471 - 0
src/engine/beta-amr-engine.js


+ 359 - 0
src/engine/beta-amr.js

@@ -0,0 +1,359 @@
+/*
+amr编码器,beta版,需带上src/engine/amr-engine.js引擎使用。如果需要播放amr音频,需要额外带上wav.js引擎来调用Recorder.amr2wav把amr转成wav播放
+https://github.com/xiangyuecn/Recorder
+
+当然最佳推荐使用mp3、wav格式,代码也是优先照顾这两种格式
+浏览器支持情况
+https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
+
+FFmpeg转码:
+[ wav->AMR-NB] ffmpeg.exe -i test.wav -ar 8000 -ab 6.7k -ac 1 amr-6.7.amr
+[ wav->AMR-NB] ffmpeg.exe -i test.wav -ar 8000 -ab 12.2k -ac 1 amr-12.2.amr
+[ wav->AMR-WB] ffmpeg.exe -i test.wav -acodec libvo_amrwbenc -ar 16000 -ab 23.85k -ac 1 amr-23.85.amr
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var BitS="4.75, 5.15, 5.9, 6.7, 7.4, 7.95, 10.2, 12.2";
+Recorder.prototype.enc_amr={
+	stable:true,takeEC:"full"
+	,getTestMsg:function(){
+		return $T("b2mN::AMR-NB(NarrowBand),采样率设置无效(只提供8000hz),比特率范围:{1}(默认12.2kbps),一帧20ms、{2}字节;浏览器一般不支持播放amr格式,可用Recorder.amr2wav()转码成wav播放",0,BitS,"Math.ceil(bitRate/8*20)+1");
+	}
+};
+
+var NormalizeSet=function(set){
+	var bS=set.bitRate,b=Recorder.AMR.BitRate(bS);
+	var sS=set.sampleRate,s=8000;
+	if(bS!=b || sS!=s) Recorder.CLog($T("tQBv::AMR Info: 和设置的不匹配{1},已更新成{2}",0,"set:"+bS+"kbps "+sS+"hz","set:"+b+"kbps "+s+"hz"),3);
+	set.bitRate=b;
+	set.sampleRate=s;
+};
+var ImportEngineErr=function(){
+	return $T.G("NeedImport-2",["beta-amr.js","src/engine/beta-amr-engine.js"]);
+};
+//是否支持web worker
+var HasWebWorker=isBrowser && typeof Worker=="function";
+
+
+/**amr转码成wav,可以直接用来播放;需同时引入src/engine/wav.js
+amrBlob: amr音频文件blob对象 或 ArrayBuffer(回调也将返回ArrayBuffer)
+True(wavBlob,duration,mime)
+False(msg)
+**/
+Recorder.amr2wav=function(amrBlob,True,False){
+	if(!Recorder.AMR){
+		False(ImportEngineErr()); return;
+	};
+	if(!Recorder.prototype.wav){
+		False($T.G("NeedImport-2",["amr2wav","src/engine/wav.js"]));
+		return;
+	};
+	
+	var loadOk=function(arrB,dArrB){
+		var amr=new Uint8Array(arrB);
+		Recorder.AMR.decode(amr,function(pcm){
+			var rec=Recorder({type:"wav"});
+			if(dArrB)rec.dataType="arraybuffer";
+			rec.mock(pcm,8000).stop(function(wavBlob,duration,mime){
+				True(wavBlob,duration,mime);
+			},False);
+		},False);
+	};
+	
+	if(amrBlob instanceof ArrayBuffer){
+		loadOk(amrBlob,1);
+	}else{
+		var reader=new FileReader();
+		reader.onloadend=function(){
+			loadOk(reader.result);
+		};
+		reader.readAsArrayBuffer(amrBlob);
+	};
+};
+
+
+//*******标准UI线程转码支持函数************
+
+Recorder.prototype.amr=function(res,True,False){
+		var This=this,set=This.set,srcSampleRate=set.sampleRate,sampleRate=8000;
+		if(!Recorder.AMR){
+			False(ImportEngineErr()); return;
+		};
+		
+		//必须先处理好采样率
+		NormalizeSet(set);
+		if(srcSampleRate>sampleRate){
+			res=Recorder.SampleData([res],srcSampleRate,sampleRate).data;
+		}else if(srcSampleRate<sampleRate){
+			False($T("q12D::数据采样率低于{1}",0,sampleRate)); return;
+		};
+		
+		//优先采用worker编码,非worker时用老方法提供兼容
+		if(HasWebWorker){
+			var ctx=This.amr_start(set);
+			if(ctx){
+				if(ctx.isW){
+					This.amr_encode(ctx,res);
+					This.amr_complete(ctx,True,False,1);
+					return;
+				}
+				This.amr_stop(ctx);
+			};
+		};
+		
+		Recorder.AMR.encode(res,function(data){
+			True(data.buffer,"audio/amr");
+		},False,set.bitRate);
+	};
+
+
+//********边录边转码(Worker)支持函数,如果提供就代表可能支持,否则只支持标准转码*********
+
+//全局共享一个Worker,后台串行执行
+var amrWorker;
+Recorder.BindDestroy("amrWorker",function(){
+	if(amrWorker){
+		Recorder.CLog("amrWorker Destroy");
+		amrWorker.terminate();
+		amrWorker=null;
+	};
+});
+
+
+Recorder.prototype.amr_envCheck=function(envInfo,set){//检查环境下配置是否可用
+	var errMsg="";
+	//需要实时编码返回数据,此时需要检查是否可实时编码
+	if(set.takeoffEncodeChunk){
+		if(!newContext()){//浏览器不能创建实时编码环境
+			errMsg=$T("TxjV::当前浏览器版本太低,无法实时处理");
+		};
+	};
+	if(!errMsg && !Recorder.AMR){
+		errMsg=ImportEngineErr();
+	};
+	return errMsg;
+};
+Recorder.prototype.amr_start=function(set){//如果返回null代表不支持
+	return newContext(set);
+};
+var openList={id:0};
+var newContext=function(setOrNull,_badW){
+	//独立运行的函数,scope.wkScope worker.onmessage 字符串会被替换
+	var run=function(e){
+		var ed=e.data;
+		var wk_ctxs=scope.wkScope.wk_ctxs;
+		var wk_AMR=scope.wkScope.wk_AMR;
+		
+		var cur=wk_ctxs[ed.id];
+		if(ed.action=="init"){
+			wk_ctxs[ed.id]={
+				takeoff:ed.takeoff
+				
+				,memory:new Uint8Array(500000), mOffset:0
+				,encObj:wk_AMR.GetEncoder(ed.bitRate)
+			};
+		}else if(!cur){
+			return;
+		};
+		var addBytes=function(buf){
+			var bufLen=buf.length;
+			if(cur.mOffset+bufLen>cur.memory.length){
+				var tmp=new Uint8Array(cur.memory.length+Math.max(500000,bufLen));
+				tmp.set(cur.memory.subarray(0, cur.mOffset));
+				cur.memory=tmp;
+			}
+			cur.memory.set(buf,cur.mOffset);
+			cur.mOffset+=bufLen;
+		};
+		
+		switch(ed.action){
+		case "stop":
+			if(!cur.isCp) try{ cur.encObj.flush() }catch(e){ console.error(e) }
+			cur.encObj=null;
+			delete wk_ctxs[ed.id];
+			break;
+		case "encode":
+			if(cur.isCp)break;
+			try{
+				var buf=cur.encObj.encode(ed.pcm);
+			}catch(e){ //精简代码调用了abort
+				cur.err=e;
+				console.error(e);
+				break;
+			};
+			if(!cur._h){//添加AMR头
+				cur._h=1;
+				var head=wk_AMR.GetHeader();
+				var buf2=new Uint8Array(head.length+buf.length);
+				buf2.set(head);
+				buf2.set(buf,head.length);
+				buf=buf2;
+			}
+			if(buf.length>0){
+				if(cur.takeoff){
+					worker.onmessage({action:"takeoff",id:ed.id,chunk:buf});
+				}else{
+					addBytes(buf);
+				};
+			};
+			break;
+		case "complete":
+			cur.isCp=1;
+			try{ cur.encObj.flush() }catch(e){ console.error(e) }; //flush没有结果,只做释放
+			if(cur.err){
+				worker.onmessage({action:ed.action,id:ed.id
+					,err:"AMR Encoder: "+cur.err.message});
+				break;
+			};
+			
+			worker.onmessage({
+				action:ed.action
+				,id:ed.id
+				,blob:cur.memory.buffer.slice(0,cur.mOffset)
+			});
+			break;
+		};
+	};
+	
+	var initOnMsg=function(isW){
+		worker.onmessage=function(e){
+			var data=e; if(isW)data=e.data;
+			var ctx=openList[data.id];
+			if(ctx){
+				if(data.action=="takeoff"){
+					//取走实时生成的amr数据
+					ctx.set.takeoffEncodeChunk(new Uint8Array(data.chunk.buffer));
+				}else{
+					//complete
+					ctx.call&&ctx.call(data);
+					ctx.call=null;
+				};
+			};
+		};
+	};
+	var initCtx=function(){
+		var ctx={worker:worker,set:setOrNull};
+		if(setOrNull){
+			ctx.id=++openList.id;
+			openList[ctx.id]=ctx;
+			
+			NormalizeSet(setOrNull);
+			var takeoff=!!setOrNull.takeoffEncodeChunk;
+			if(takeoff){
+				Recorder.CLog($T("Q7p7::takeoffEncodeChunk接管AMR编码器输出的二进制数据,只有首次回调数据(首帧)包含AMR头;在合并成AMR文件时,如果没有把首帧数据包含进去,则必须在文件开头添加上AMR头:Recorder.AMR.AMR_HEADER(转成二进制),否则无法播放"),3);
+			};
+			
+			worker.postMessage({
+				action:"init"
+				,id:ctx.id
+				,sampleRate:setOrNull.sampleRate
+				,bitRate:setOrNull.bitRate
+				,takeoff:takeoff
+				
+				,x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray
+			});
+		}else{
+			worker.postMessage({
+				x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray
+			});
+		};
+		return ctx;
+	};
+	var scope,worker=amrWorker;
+	
+	//非浏览器,不支持worker,或者开启失败,使用UI线程处理
+	if(_badW || !HasWebWorker){
+		Recorder.CLog($T("6o9Z::当前环境不支持Web Worker,amr实时编码器运行在主线程中"),3);
+		worker={ postMessage:function(ed){ run({data:ed}); } };
+		scope={wkScope:{
+			wk_ctxs:{}, wk_AMR:Recorder.AMR
+		}};
+		initOnMsg();
+		return initCtx();
+	};
+	
+	try{
+		if(!worker){
+			//创建一个新Worker
+			var onmsg=(run+"").replace(/[\w\$]+\.onmessage/g,"self.postMessage");
+			onmsg=onmsg.replace(/[\w\$]+\.wkScope/g,"wkScope");
+			var jsCode=");self.onmessage="+onmsg;
+			jsCode+=";var wkScope={ wk_ctxs:{},wk_AMR:Create() }";
+			
+			var engineCode=Recorder.AMR.Create.toString();
+			var url=(window.URL||webkitURL).createObjectURL(new Blob(["var Create=(",engineCode,jsCode], {type:"text/javascript"}));
+			
+			worker=new Worker(url);
+			setTimeout(function(){
+				(window.URL||webkitURL).revokeObjectURL(url);//必须要释放,不然每次调用内存都明显泄露内存
+			},10000);//chrome 83 file协议下如果直接释放,将会使WebWorker无法启动
+			initOnMsg(1);
+		};
+		
+		var ctx=initCtx(); ctx.isW=1;
+		amrWorker=worker;
+		return ctx;
+	}catch(e){//出错了就不要提供了
+		worker&&worker.terminate();
+		console.error(e);
+		return newContext(setOrNull, 1);//切换到UI线程处理
+	};
+};
+Recorder.prototype.amr_stop=function(startCtx){
+	if(startCtx&&startCtx.worker){
+		startCtx.worker.postMessage({
+			action:"stop"
+			,id:startCtx.id
+		});
+		startCtx.worker=null;
+		delete openList[startCtx.id];
+		
+		//疑似泄露检测 排除id
+		var opens=-1;
+		for(var k in openList){
+			opens++;
+		};
+		if(opens){
+			Recorder.CLog($T("yYWs::amr worker剩{1}个未stop",0,opens),3);
+		};
+	};
+};
+Recorder.prototype.amr_encode=function(startCtx,pcm){
+	if(startCtx&&startCtx.worker){
+		startCtx.worker.postMessage({
+			action:"encode"
+			,id:startCtx.id
+			,pcm:pcm
+		});
+	};
+};
+Recorder.prototype.amr_complete=function(startCtx,True,False,autoStop){
+	var This=this;
+	if(startCtx&&startCtx.worker){
+		startCtx.call=function(data){
+			if(autoStop){
+				This.amr_stop(startCtx);
+			};
+			if(data.err){
+				False(data.err);
+			}else{
+				True(data.blob,"audio/amr");
+			};
+		};
+		startCtx.worker.postMessage({
+			action:"complete"
+			,id:startCtx.id
+		});
+	}else{
+		False($T("jOi8::amr编码器未start"));
+	};
+};
+
+
+}));

Dosya farkı çok büyük olduğundan ihmal edildi
+ 202 - 0
src/engine/beta-ogg-engine.js


+ 367 - 0
src/engine/beta-ogg.js

@@ -0,0 +1,367 @@
+/*
+ogg编码器,beta版,需带上src/engine/beta-ogg-engine.js引擎使用
+https://github.com/xiangyuecn/Recorder
+
+当然最佳推荐使用mp3、wav格式,代码也是优先照顾这两种格式
+浏览器支持情况
+https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+Recorder.prototype.enc_ogg={
+	stable:true,takeEC:"slow"
+	,getTestMsg:function(){
+		return $T("O8Gn::Ogg Vorbis,比特率取值16-100kbps,采样率取值无限制");
+	}
+};
+
+var ImportEngineErr=function(){
+	return $T.G("NeedImport-2",["beta-ogg.js","src/engine/beta-ogg-engine.js"]);
+};
+//是否支持web worker
+var HasWebWorker=isBrowser && typeof Worker=="function";
+
+
+//*******标准UI线程转码支持函数************
+
+Recorder.prototype.ogg=function(res,True,False){
+		var This=this,set=This.set,size=res.length,bitRate=set.bitRate;
+		if(!Recorder.OggVorbisEncoder){
+			False(ImportEngineErr()); return;
+		};
+		
+		//优先采用worker编码,非worker时用老方法提供兼容
+		if(HasWebWorker){
+			var ctx=This.ogg_start(set);
+			if(ctx){
+				if(ctx.isW){
+					This.ogg_encode(ctx,res);
+					This.ogg_complete(ctx,True,False,1);
+					return;
+				}
+				This.ogg_stop(ctx);
+			};
+		};
+		
+		
+		var bitV=GetBitRate(bitRate);
+		set.bitRate=bitV.bitRate;
+		
+		var ogg = new Recorder.OggVorbisEncoder(set.sampleRate, 1, bitV.val);
+		
+		var blockSize=set.sampleRate;
+		var memory=new Uint8Array(500000), mOffset=0;
+		
+		var idx=0,isFlush=0;
+		var run=function(){
+			try{
+				if(idx<size){
+					var buf=ogg.encode([res.subarray(idx,idx+blockSize)]);
+				}else{
+					isFlush=1;
+					var buf=ogg.flush();
+				};
+			}catch(e){ //精简代码调用了abort
+				console.error(e);
+				if(!isFlush) try{ ogg.flush() }catch(r){ console.error(r) }
+				False("Ogg Encoder: "+e.message);
+				return;
+			};
+			if(buf.length>0){
+				var bufLen=buf.length;
+				if(mOffset+bufLen>memory.length){
+					var tmp=new Uint8Array(memory.length+Math.max(500000,bufLen));
+					tmp.set(memory.subarray(0, mOffset));
+					memory=tmp;
+				}
+				memory.set(buf,mOffset);
+				mOffset+=bufLen;
+			};
+			
+			if(idx<size){
+				idx+=blockSize;
+				setTimeout(run);//尽量避免卡ui
+			}else{
+				True(memory.buffer.slice(0,mOffset),"audio/ogg");
+			};
+		};
+		run();
+	}
+
+
+//********边录边转码(Worker)支持函数,如果提供就代表可能支持,否则只支持标准转码*********
+
+//全局共享一个Worker,后台串行执行。如果每次都开一个新的,编码速度可能会慢很多,可能是浏览器运行缓存的因素,并且可能瞬间产生多个并行操作占用大量cpu
+var oggWorker;
+Recorder.BindDestroy("oggWorker",function(){
+	if(oggWorker){
+		Recorder.CLog("oggWorker Destroy");
+		oggWorker.terminate();
+		oggWorker=null;
+	};
+});
+
+
+Recorder.prototype.ogg_envCheck=function(envInfo,set){//检查环境下配置是否可用
+	var errMsg="";
+	//需要实时编码返回数据,此时需要检查是否可实时编码
+	if(set.takeoffEncodeChunk){
+		if(!newContext()){//浏览器不能创建实时编码环境
+			errMsg=$T("5si6::当前浏览器版本太低,无法实时处理");
+		};
+	};
+	if(!errMsg && !Recorder.OggVorbisEncoder){
+		errMsg=ImportEngineErr();
+	};
+	return errMsg;
+};
+Recorder.prototype.ogg_start=function(set){//如果返回null代表不支持
+	return newContext(set);
+};
+var openList={id:0};
+var newContext=function(setOrNull,_badW){
+	//独立运行的函数,scope.wkScope worker.onmessage 字符串会被替换
+	var run=function(e){
+		var ed=e.data;
+		var wk_ctxs=scope.wkScope.wk_ctxs;
+		var wk_OggEnc=scope.wkScope.wk_OggEnc;
+		
+		var cur=wk_ctxs[ed.id];
+		if(ed.action=="init"){
+			wk_ctxs[ed.id]={
+				takeoff:ed.takeoff
+				
+				,memory:new Uint8Array(500000), mOffset:0
+				,encObj:new wk_OggEnc(ed.sampleRate, 1, ed.bitVv)
+			};
+		}else if(!cur){
+			return;
+		};
+		var addBytes=function(buf){
+			var bufLen=buf.length;
+			if(cur.mOffset+bufLen>cur.memory.length){
+				var tmp=new Uint8Array(cur.memory.length+Math.max(500000,bufLen));
+				tmp.set(cur.memory.subarray(0, cur.mOffset));
+				cur.memory=tmp;
+			}
+			cur.memory.set(buf,cur.mOffset);
+			cur.mOffset+=bufLen;
+		};
+		
+		switch(ed.action){
+		case "stop":
+			if(!cur.isCp) try{ cur.encObj.flush() }catch(e){ console.error(e) }
+			cur.encObj=null;
+			delete wk_ctxs[ed.id];
+			break;
+		case "encode":
+			if(cur.isCp)break;
+			try{
+				var buf=cur.encObj.encode([ed.pcm]);
+			}catch(e){ //精简代码调用了abort
+				cur.err=e;
+				console.error(e);
+			};
+			if(buf && buf.length>0){
+				if(cur.takeoff){
+					worker.onmessage({action:"takeoff",id:ed.id,chunk:buf});
+				}else{
+					addBytes(buf);
+				};
+			};
+			break;
+		case "complete":
+			cur.isCp=1;
+			try{
+				var buf=cur.encObj.flush();
+			}catch(e){ //精简代码调用了abort
+				cur.err=e;
+				console.error(e);
+			};
+			if(buf && buf.length>0){
+				if(cur.takeoff){
+					worker.onmessage({action:"takeoff",id:ed.id,chunk:buf});
+				}else{
+					addBytes(buf);
+				};
+			};
+			if(cur.err){
+				worker.onmessage({action:ed.action,id:ed.id
+					,err:"Ogg Encoder: "+cur.err.message});
+				break;
+			};
+			
+			worker.onmessage({
+				action:ed.action
+				,id:ed.id
+				,blob:cur.memory.buffer.slice(0,cur.mOffset)
+			});
+			break;
+		};
+	};
+	
+	var initOnMsg=function(isW){
+		worker.onmessage=function(e){
+			var data=e; if(isW)data=e.data;
+			var ctx=openList[data.id];
+			if(ctx){
+				if(data.action=="takeoff"){
+					//取走实时生成的ogg数据
+					ctx.set.takeoffEncodeChunk(new Uint8Array(data.chunk.buffer));
+				}else{
+					//complete
+					ctx.call&&ctx.call(data);
+					ctx.call=null;
+				};
+			};
+		};
+	};
+	var initCtx=function(){
+		var ctx={worker:worker,set:setOrNull};
+		if(setOrNull){
+			ctx.id=++openList.id;
+			openList[ctx.id]=ctx;
+			
+			var bitV=GetBitRate(setOrNull.bitRate);
+			setOrNull.bitRate=bitV.bitRate;
+			
+			var takeoff=!!setOrNull.takeoffEncodeChunk;
+			if(takeoff){
+				Recorder.CLog($T("R8yz::takeoffEncodeChunk接管OggVorbis编码器输出的二进制数据,Ogg由数据页组成,一页包含多帧音频数据(含几秒的音频,一页数据无法单独解码和播放),此编码器每次输出都是完整的一页数据,因此实时性会比较低;在合并成完整ogg文件时,必须将输出的所有数据合并到一起,否则可能无法播放,不支持截取中间一部分单独解码和播放"),3);
+			};
+			
+			worker.postMessage({
+				action:"init"
+				,id:ctx.id
+				,sampleRate:setOrNull.sampleRate
+				,bitRate:setOrNull.bitRate, bitVv:bitV.val
+				,takeoff:takeoff
+				
+				,x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray
+			});
+		}else{
+			worker.postMessage({
+				x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray
+			});
+		};
+		return ctx;
+	};
+	var scope,worker=oggWorker;
+	
+	//非浏览器,不支持worker,或者开启失败,使用UI线程处理
+	if(_badW || !HasWebWorker){
+		Recorder.CLog($T("hB9D::当前环境不支持Web Worker,OggVorbis实时编码器运行在主线程中"),3);
+		worker={ postMessage:function(ed){ run({data:ed}); } };
+		scope={wkScope:{
+			wk_ctxs:{}, wk_OggEnc:Recorder.OggVorbisEncoder
+		}};
+		initOnMsg();
+		return initCtx();
+	};
+	
+	try{
+		if(!worker){
+			//创建一个新Worker
+			var onmsg=(run+"").replace(/[\w\$]+\.onmessage/g,"self.postMessage");
+			onmsg=onmsg.replace(/[\w\$]+\.wkScope/g,"wkScope");
+			var jsCode=");self.onmessage="+onmsg;
+			jsCode+=";var wkScope={ wk_ctxs:{},wk_OggEnc:Create() };";
+			if(Recorder.OggVorbisEncoder.Module.StaticSeed){
+				jsCode+="wkScope.wk_OggEnc.Module.StaticSeed=true;";
+			};
+			
+			var engineCode=Recorder.OggVorbisEncoder.Create.toString();
+			var url=(window.URL||webkitURL).createObjectURL(new Blob(["var Create=(",engineCode,jsCode], {type:"text/javascript"}));
+			
+			worker=new Worker(url);
+			setTimeout(function(){
+				(window.URL||webkitURL).revokeObjectURL(url);//必须要释放,不然每次调用内存都明显泄露内存
+			},10000);//chrome 83 file协议下如果直接释放,将会使WebWorker无法启动
+			initOnMsg(1);
+		};
+		
+		var ctx=initCtx(); ctx.isW=1;
+		oggWorker=worker;
+		return ctx;
+	}catch(e){//出错了就不要提供了
+		worker&&worker.terminate();
+		console.error(e);
+		return newContext(setOrNull, 1);//切换到UI线程处理
+	};
+};
+Recorder.prototype.ogg_stop=function(startCtx){
+	if(startCtx&&startCtx.worker){
+		startCtx.worker.postMessage({
+			action:"stop"
+			,id:startCtx.id
+		});
+		startCtx.worker=null;
+		delete openList[startCtx.id];
+		
+		//疑似泄露检测 排除id
+		var opens=-1;
+		for(var k in openList){
+			opens++;
+		};
+		if(opens){
+			Recorder.CLog($T("oTiy::ogg worker剩{1}个未stop",0,opens),3);
+		};
+	};
+};
+Recorder.prototype.ogg_encode=function(startCtx,pcm){
+	if(startCtx&&startCtx.worker){
+		startCtx.worker.postMessage({
+			action:"encode"
+			,id:startCtx.id
+			,pcm:pcm
+		});
+	};
+};
+Recorder.prototype.ogg_complete=function(startCtx,True,False,autoStop){
+	var This=this;
+	if(startCtx&&startCtx.worker){
+		startCtx.call=function(data){
+			if(autoStop){
+				This.ogg_stop(startCtx);
+			};
+			if(data.err){
+				False(data.err);
+			}else{
+				True(data.blob,"audio/ogg");
+			};
+		};
+		startCtx.worker.postMessage({
+			action:"complete"
+			,id:startCtx.id
+		});
+	}else{
+		False($T("dIpw::ogg编码器未start"));
+	};
+};
+
+
+
+
+
+
+/* 编码输出测试,半天才输出一段数据
+var ogg = new Recorder.OggVorbisEncoder(16000, 1, -0.1);
+for(var i=0;i<10*10;i++){v=ogg.encode([new Array(1600).fill().map(a=>~~(Math.random()*0x7fff))]).length; if(v)console.log(i,v)}
+console.log("flush");ogg.flush().length;
+*/
+
+//转换比特率成质量数值
+var GetBitRate=function(bitRate){
+	bitRate=Math.min(Math.max(bitRate,16),100);
+	//取值-0.1-1,实际输出16-100kbps
+	var val=Math.max(1.1*(bitRate-16)/(100-16)-0.1, -0.1);
+	return {bitRate:bitRate,val:val};
+};
+
+}));

+ 86 - 0
src/engine/beta-webm.js

@@ -0,0 +1,86 @@
+/*
+webm编码器,beta版
+https://github.com/xiangyuecn/Recorder
+
+当然最佳推荐使用mp3、wav格式,代码也是优先照顾这两种格式
+浏览器支持情况
+https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var mime="audio/webm";
+var support=isBrowser&&window.MediaRecorder&&MediaRecorder.isTypeSupported(mime);
+
+
+Recorder.prototype.enc_webm={
+	stable:false
+	,getTestMsg:function(){
+		if(!support) return $T("L49q::此浏览器不支持进行webm编码,未实现MediaRecorder");
+		return $T("tsTW::只有比较新的浏览器支持,压缩率和mp3差不多。由于未找到对已有pcm数据进行快速编码的方法,只能按照类似边播放边收听形式把数据导入到MediaRecorder,有几秒就要等几秒。输出音频虽然可以通过比特率来控制文件大小,但音频文件中的比特率并非设定比特率,采样率由于是我们自己采样的,到这个编码器随他怎么搞");
+	}
+};
+Recorder.prototype.webm=function(res,True,False){
+		//https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder
+		//https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamAudioDestinationNode
+		if(!isBrowser){
+			False($T.G("NonBrowser-1",["webm encoder"]));
+			return;
+		};
+		if(!support){
+			False($T("aG4z::此浏览器不支持把录音转成webm格式"));
+			return;
+		};
+		var This=this, set=This.set,size=res.length,sampleRate=set.sampleRate;
+		
+		var ctx=Recorder.Ctx;
+		
+		var dest=ctx.createMediaStreamDestination();
+		dest.channelCount=1;
+		
+		//录音啦
+		var recorder = new MediaRecorder(dest.stream,{
+			mimeType:mime
+			,bitsPerSecond:set.bitRate*1000
+		});
+		var chunks = [];
+		recorder.ondataavailable=function(e) {
+			chunks.push(e.data);
+		};
+		recorder.onstop=function(e) {
+			var blob=new Blob(chunks,{type:mime});
+			var reader=new FileReader();
+			reader.onloadend=function(){
+				True(reader.result,mime);
+			};
+			reader.readAsArrayBuffer(blob);
+		};
+		recorder.onerror=function(e){
+			False($T("PIX0::转码webm出错:{1}",0,e.message));
+		};
+		recorder.start();
+		
+		//声音源
+		var buffer=ctx.createBuffer(1,size,sampleRate);
+		var buffer0=buffer.getChannelData(0);
+		for(var j=0;j<size;j++){
+			var s=res[j];
+			s=s<0?s/0x8000:s/0x7FFF;
+			buffer0[j]=s;
+		};
+		var source=ctx.createBufferSource();
+		source.channelCount=1;
+		source.buffer=buffer;
+		source.connect(dest);
+		if(source.start){source.start()}else{source.noteOn(0)};
+		source.onended=function(){
+			recorder.stop();
+		};
+	}
+	
+}));

+ 236 - 0
src/engine/g711x.js

@@ -0,0 +1,236 @@
+/*
+g711x编码器+解码器
+https://github.com/xiangyuecn/Recorder
+
+可用type:
+	g711a: G.711 A-law (pcma)
+	g711u: G.711 μ-law (pcmu、mu-law)
+
+编解码源码移植自:https://github.com/twstx1/codec-for-audio-in-G72X-G711-G723-G726-G729/blob/master/G711_G721_G723/g711.c
+移植相关测试代码(FFmpeg转码、播放命令):assets/runtime-codes/test.g7xx.engine.js
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var regEngine=function(key,desc,enc,dec){
+
+Recorder.prototype["enc_"+key]={
+	stable:true,fast:true
+	,getTestMsg:function(){
+		return $T("d8YX::{1};{2}音频文件无法直接播放,可用Recorder.{2}2wav()转码成wav播放;采样率比特率设置无效,固定为8000hz采样率、16位,每个采样压缩成8位存储,音频文件大小为8000字节/秒;如需任意采样率支持,请使用Recorder.{2}_encode()方法",0,desc,key);
+	}
+};
+
+//*******标准UI线程转码支持函数************
+Recorder.prototype[key]=function(res,True,False){
+	var This=this,set=This.set,srcSampleRate=set.sampleRate,sampleRate=8000;
+	set.bitRate=16;
+	set.sampleRate=sampleRate;
+	if(srcSampleRate>sampleRate){
+		res=Recorder.SampleData([res],srcSampleRate,sampleRate).data;
+	}else if(srcSampleRate<sampleRate){
+		False($T("29UK::数据采样率低于{1}",0,sampleRate)); return;
+	};
+	var bytes=enc(res);
+	True(bytes.buffer,"audio/"+key);
+};
+
+/**编码任意采样率的pcm得到g711x数据
+pcm: Int16Array,任意采样率pcm数据(标准采样率为8000)
+返回Uint8Array,g711x二进制数据(采样率为pcm的采样率)
+**/
+Recorder[key+"_encode"]=function(pcm){
+	return enc(pcm);
+};
+/**解码g711x得到pcm
+bytes: Uint8Array,g711x二进制数据,采样率一般是8000
+返回Int16Array,为g711x的采样率、16位的pcm数据
+**/
+Recorder[key+"_decode"]=function(bytes){
+	return dec(bytes);
+};
+
+/**g711x直接转码成wav,可以直接用来播放;需同时引入src/engine/wav.js
+g711xBlob: g711x音频文件blob对象 或 ArrayBuffer(回调也将返回ArrayBuffer),采样率只支持8000
+True(wavBlob,duration,mime)
+False(msg)
+**/
+Recorder[key+"2wav"]=function(g711xBlob,True,False){
+	if(!Recorder.prototype.wav){
+		False($T.G("NeedImport-2",[key+"2wav","src/engine/wav.js"]));
+		return;
+	};
+	
+	var loadOk=function(arrB,dArrB){
+		var bytes=new Uint8Array(arrB);
+		var pcm=dec(bytes);
+		
+		var rec=Recorder({
+			type:"wav",sampleRate:8000,bitRate:16
+		});
+		if(dArrB)rec.dataType="arraybuffer";
+		rec.mock(pcm,8000).stop(function(wavBlob,duration,mime){
+			True(wavBlob,duration,mime);
+		},False);
+	};
+	
+	if(g711xBlob instanceof ArrayBuffer){
+		loadOk(g711xBlob,1);
+	}else{
+		var reader=new FileReader();
+		reader.onloadend=function(){
+			loadOk(reader.result);
+		};
+		reader.readAsArrayBuffer(g711xBlob);
+	};
+};
+
+
+
+
+//********边录边转码支持函数,g711转码超快,因此也是工作在UI线程(非Worker)*********
+Recorder.prototype[key+"_envCheck"]=function(envInfo,set){//检查环境下配置是否可用
+	return ""; //没有需要检查的内容
+};
+
+Recorder.prototype[key+"_start"]=function(set){//如果返回null代表不支持
+	set.bitRate=16;
+	set.sampleRate=8000;
+	return {set:set, memory:new Uint8Array(500000), mOffset:0};
+};
+var addBytes=function(cur,buf){
+	var bufLen=buf.length;
+	if(cur.mOffset+bufLen>cur.memory.length){
+		var tmp=new Uint8Array(cur.memory.length+Math.max(500000,bufLen));
+		tmp.set(cur.memory.subarray(0, cur.mOffset));
+		cur.memory=tmp;
+	}
+	cur.memory.set(buf,cur.mOffset);
+	cur.mOffset+=bufLen;
+};
+
+Recorder.prototype[key+"_stop"]=function(startCtx){
+	if(startCtx&&startCtx.memory){
+		startCtx.memory=null;
+	}
+};
+Recorder.prototype[key+"_encode"]=function(startCtx,pcm){
+	if(startCtx&&startCtx.memory){
+		var set=startCtx.set;
+		var bytes=enc(pcm);
+		
+		if(set.takeoffEncodeChunk){
+			set.takeoffEncodeChunk(bytes);
+		}else{
+			addBytes(startCtx, bytes);
+		};
+	};
+};
+Recorder.prototype[key+"_complete"]=function(startCtx,True,False,autoStop){
+	if(startCtx&&startCtx.memory){
+		if(autoStop){
+			this[key+"_stop"](startCtx);
+		};
+		var buffer=startCtx.memory.buffer.slice(0,startCtx.mOffset);
+		True(buffer,"audio/"+key);
+	}else{
+		False($T("quVJ::{1}编码器未start",0,key));
+	};
+};
+
+};
+
+
+
+var Tab=[1,2,3,3,4,4,4,4,5,5,5,5,5,5,5,5,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7];
+
+regEngine("g711a","G.711 A-law (pcma)"
+,function(pcm){//编码
+	var buffer=new Uint8Array(pcm.length);
+	for(var i=0;i<pcm.length;i++){
+		var pcm_val=pcm[i],mask;
+
+		if (pcm_val >= 0) {
+			mask = 0xD5;		/* sign (7th) bit = 1 */
+		} else {
+			mask = 0x55;		/* sign bit = 0 */
+			pcm_val = -pcm_val - 1;
+		}
+
+		/* Convert the scaled magnitude to segment number. */
+		var seg = (Tab[pcm_val>>8&0x7F]||8)-1;
+		
+		/* Combine the sign, segment, and quantization bits. */
+		var aval = seg << 4;
+		if (seg < 2)
+			aval |= (pcm_val >> 4) & 15;
+		else
+			aval |= (pcm_val >> (seg + 3)) & 15;
+		buffer[i] = (aval ^ mask);
+	}
+	return buffer;
+}
+,function(bytes){//解码
+	var buffer=new Int16Array(bytes.length);
+	for(var i=0;i<bytes.length;i++){
+		var a_val=bytes[i]^0x55;
+		var t = (a_val & 15) << 4;
+		var seg = (a_val & 0x70) >> 4;
+		switch (seg) {
+		case 0:
+			t += 8; break;
+		case 1:
+			t += 0x108; break;
+		default:
+			t += 0x108;
+			t <<= seg - 1;
+		}
+		buffer[i] = ((a_val & 0x80) ? t : -t);
+	}
+	return buffer;
+});
+
+
+regEngine("g711u","G.711 μ-law (pcmu、mu-law)"
+,function(pcm){//编码
+	var buffer=new Uint8Array(pcm.length);
+	for(var i=0;i<pcm.length;i++){
+		var pcm_val=pcm[i],mask;
+		
+		/* Get the sign and the magnitude of the value. */
+		if (pcm_val < 0) {
+			pcm_val = 0x84 - pcm_val;
+			mask = 0x7F;
+		} else {
+			pcm_val += 0x84;
+			mask = 0xFF;
+		}
+		
+		/* Convert the scaled magnitude to segment number. */
+		var seg = (Tab[pcm_val>>8&0x7F]||8)-1;
+		
+		var uval = (seg << 4) | ((pcm_val >> (seg + 3)) & 0xF);
+		buffer[i] = (uval ^ mask);
+	}
+	return buffer;
+}
+,function(bytes){//解码
+	var buffer=new Int16Array(bytes.length);
+	for(var i=0;i<bytes.length;i++){
+		var u_val= ~bytes[i];
+		
+		var t = ((u_val & 15) << 3) + 0x84;
+		t <<= (u_val & 0x70) >> 4;
+
+		buffer[i] = ((u_val & 0x80) ? (0x84 - t) : (t - 0x84));
+	}
+	return buffer;
+});
+
+
+}));

+ 11401 - 0
src/engine/mp3-engine.js

@@ -0,0 +1,11401 @@
+/*
+mp3编码器用到的lamejs编码引擎,一般都用MP3格式,浏览器支持广泛,此引擎测试的也比较多,稳定
+https://github.com/xiangyuecn/Recorder
+
+由此源码改动而来 (2023-09-25 大幅精简代码,移除了大量从未调用的函数、条件分支)(2019-11-03 精简代码)
+https://github.com/zhuker/lamejs/blob/bfb7f6c6d7877e0fe1ad9e72697a871676119a0e/lame.all.js
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder;
+	factory(rec);
+}(function(Recorder){ //需要在Worker中运行,不能使用Recorder里的方法,包括$T
+"use strict";
+
+function lamejs() {
+var Math_log10=function(s){//坚决不能用也不要报语言问题的错误
+	return Math.log(s)/Math.log(10);
+};
+var abort=function(what){
+	throw new Error("abort(" + what + ")")
+};
+
+function new_byte(count) {
+    return new Int8Array(count);
+}
+
+function new_short(count) {
+    return new Int16Array(count);
+}
+
+function new_int(count) {
+    return new Int32Array(count);
+}
+
+function new_float(count) {
+    return new Float32Array(count);
+}
+
+function new_double(count) {
+    return new Float64Array(count);
+}
+
+function new_float_n(args) {
+    if (args.length == 1) {
+        return new_float(args[0]);
+    }
+    var sz = args[0];
+    args = args.slice(1);
+    var A = [];
+    for (var i = 0; i < sz; i++) {
+        A.push(new_float_n(args));
+    }
+    return A;
+}
+function new_int_n(args) {
+    if (args.length == 1) {
+        return new_int(args[0]);
+    }
+    var sz = args[0];
+    args = args.slice(1);
+    var A = [];
+    for (var i = 0; i < sz; i++) {
+        A.push(new_int_n(args));
+    }
+    return A;
+}
+
+function new_short_n(args) {
+    if (args.length == 1) {
+        return new_short(args[0]);
+    }
+    var sz = args[0];
+    args = args.slice(1);
+    var A = [];
+    for (var i = 0; i < sz; i++) {
+        A.push(new_short_n(args));
+    }
+    return A;
+}
+
+function new_array_n(args) {
+    if (args.length == 1) {
+        return new Array(args[0]);
+    }
+    var sz = args[0];
+    args = args.slice(1);
+    var A = [];
+    for (var i = 0; i < sz; i++) {
+        A.push(new_array_n(args));
+    }
+    return A;
+}
+
+
+var Arrays = {};
+
+Arrays.fill = function (a, fromIndex, toIndex, val) {
+    if (arguments.length == 2) {
+        for (var i = 0; i < a.length; i++) {
+            a[i] = arguments[1];
+        }
+    } else {
+        for (var i = fromIndex; i < toIndex; i++) {
+            a[i] = val;
+        }
+    }
+};
+
+var System = {};
+
+System.arraycopy = function (src, srcPos, dest, destPos, length) {
+    var srcEnd = srcPos + length;
+    while (srcPos < srcEnd)
+        dest[destPos++] = src[srcPos++];
+};
+
+
+var Util = {};
+Util.SQRT2 = 1.41421356237309504880;
+Util.FAST_LOG10 = function (x) {
+    return Math_log10(x);
+};
+
+Util.FAST_LOG10_X = function (x, y) {
+    return Math_log10(x) * y;
+};
+
+function ShortBlock(ordinal) {
+    this.ordinal = ordinal;
+}
+/**
+ * LAME may use them, even different block types for L/R.
+ */
+ShortBlock.short_block_allowed = new ShortBlock(0);
+/**
+ * LAME may use them, but always same block types in L/R.
+ */
+ShortBlock.short_block_coupled = new ShortBlock(1);
+/**
+ * LAME will not use short blocks, long blocks only.
+ */
+ShortBlock.short_block_dispensed = new ShortBlock(2);
+/**
+ * LAME will not use long blocks, short blocks only.
+ */
+ShortBlock.short_block_forced = new ShortBlock(3);
+
+var Float = {};
+Float.MAX_VALUE = 3.4028235e+38;
+
+function VbrMode(ordinal) {
+    this.ordinal = ordinal;
+}
+VbrMode.vbr_off = new VbrMode(0);
+VbrMode.vbr_mt = new VbrMode(1);
+VbrMode.vbr_rh = new VbrMode(2);
+VbrMode.vbr_abr = new VbrMode(3);
+VbrMode.vbr_mtrh = new VbrMode(4);
+VbrMode.vbr_default = VbrMode.vbr_mtrh;
+
+var assert = function (x) {
+    abort(x);
+};
+
+var module_exports = {
+    "System": System,
+    "VbrMode": VbrMode,
+    "Float": Float,
+    "ShortBlock": ShortBlock,
+    "Util": Util,
+    "Arrays": Arrays,
+    "new_array_n": new_array_n,
+    "new_byte": new_byte,
+    "new_double": new_double,
+    "new_float": new_float,
+    "new_float_n": new_float_n,
+    "new_int": new_int,
+    "new_int_n": new_int_n,
+    "new_short": new_short,
+    "new_short_n": new_short_n,
+    "assert": assert
+};
+//package mp3;
+
+/* MPEG modes */
+function MPEGMode(ordinal) {
+    var _ordinal = ordinal;
+    this.ordinal = function () {
+        return _ordinal;
+    }
+}
+
+MPEGMode.STEREO = new MPEGMode(0);
+MPEGMode.JOINT_STEREO = new MPEGMode(1);
+MPEGMode.DUAL_CHANNEL = new MPEGMode(2);
+MPEGMode.MONO = new MPEGMode(3);
+MPEGMode.NOT_SET = new MPEGMode(4);
+
+function Version() {
+
+    /**
+     * URL for the LAME website.
+     */
+    //var LAME_URL = "http://www.mp3dev.org/";
+
+    /**
+     * Major version number.
+     */
+    var LAME_MAJOR_VERSION = 3;
+    /**
+     * Minor version number.
+     */
+    var LAME_MINOR_VERSION = 98;
+    /**
+     * Patch level.
+     */
+    var LAME_PATCH_VERSION = 4;
+
+    //fix cc 精简
+
+    /**
+     * The short version of the LAME version string.
+     *
+     * @return short version of the LAME version string
+     */
+    this.getLameShortVersion = function () {
+        // Adding date and time to version string makes it harder for output
+        // validation
+        return (LAME_MAJOR_VERSION + "." + LAME_MINOR_VERSION + "." + LAME_PATCH_VERSION);
+    }
+
+    //fix cc 精简
+
+}
+
+/*
+ *	MP3 huffman table selecting and bit counting
+ *
+ *	Copyright (c) 1999-2005 Takehiro TOMINAGA
+ *	Copyright (c) 2002-2005 Gabriel Bouvigne
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* $Id: Takehiro.java,v 1.26 2011/05/24 20:48:06 kenchis Exp $ */
+
+//package mp3;
+
+//import java.util.Arrays;
+
+
+
+function Takehiro() {
+
+    var qupvt = null;
+    this.qupvt = null;
+
+    this.setModules = function (_qupvt) {
+        this.qupvt = _qupvt;
+        qupvt = _qupvt;
+    }
+
+    function Bits(b) {
+        this.bits = 0 | b;
+    }
+
+    var subdv_table = [[0, 0], /* 0 bands */
+        [0, 0], /* 1 bands */
+        [0, 0], /* 2 bands */
+        [0, 0], /* 3 bands */
+        [0, 0], /* 4 bands */
+        [0, 1], /* 5 bands */
+        [1, 1], /* 6 bands */
+        [1, 1], /* 7 bands */
+        [1, 2], /* 8 bands */
+        [2, 2], /* 9 bands */
+        [2, 3], /* 10 bands */
+        [2, 3], /* 11 bands */
+        [3, 4], /* 12 bands */
+        [3, 4], /* 13 bands */
+        [3, 4], /* 14 bands */
+        [4, 5], /* 15 bands */
+        [4, 5], /* 16 bands */
+        [4, 6], /* 17 bands */
+        [5, 6], /* 18 bands */
+        [5, 6], /* 19 bands */
+        [5, 7], /* 20 bands */
+        [6, 7], /* 21 bands */
+        [6, 7], /* 22 bands */
+    ];
+
+
+    /**
+     * nonlinear quantization of xr More accurate formula than the ISO formula.
+     * Takes into account the fact that we are quantizing xr . ix, but we want
+     * ix^4/3 to be as close as possible to x^4/3. (taking the nearest int would
+     * mean ix is as close as possible to xr, which is different.)
+     *
+     * From Segher Boessenkool <segher@eastsite.nl> 11/1999
+     *
+     * 09/2000: ASM code removed in favor of IEEE754 hack by Takehiro Tominaga.
+     * If you need the ASM code, check CVS circa Aug 2000.
+     *
+     * 01/2004: Optimizations by Gabriel Bouvigne
+     */
+    function quantize_lines_xrpow_01(l, istep, xr, xrPos, ix, ixPos) {
+        var compareval0 = (1.0 - 0.4054) / istep;
+
+        l = l >> 1;
+        while ((l--) != 0) {
+            ix[ixPos++] = (compareval0 > xr[xrPos++]) ? 0 : 1;
+            ix[ixPos++] = (compareval0 > xr[xrPos++]) ? 0 : 1;
+        }
+    }
+
+    /**
+     * XRPOW_FTOI is a macro to convert floats to ints.<BR>
+     * if XRPOW_FTOI(x) = nearest_int(x), then QUANTFAC(x)=adj43asm[x]<BR>
+     * ROUNDFAC= -0.0946<BR>
+     *
+     * if XRPOW_FTOI(x) = floor(x), then QUANTFAC(x)=asj43[x]<BR>
+     * ROUNDFAC=0.4054<BR>
+     *
+     * Note: using floor() or 0| is extremely slow. On machines where the
+     * TAKEHIRO_IEEE754_HACK code above does not work, it is worthwile to write
+     * some ASM for XRPOW_FTOI().
+     */
+    function quantize_lines_xrpow(l, istep, xr, xrPos, ix, ixPos) {
+
+        l = l >> 1;
+        var remaining = l % 2;
+        l = l >> 1;
+        while (l-- != 0) {
+            var x0, x1, x2, x3;
+            var rx0, rx1, rx2, rx3;
+
+            x0 = xr[xrPos++] * istep;
+            x1 = xr[xrPos++] * istep;
+            rx0 = 0 | x0;
+            x2 = xr[xrPos++] * istep;
+            rx1 = 0 | x1;
+            x3 = xr[xrPos++] * istep;
+            rx2 = 0 | x2;
+            x0 += qupvt.adj43[rx0];
+            rx3 = 0 | x3;
+            x1 += qupvt.adj43[rx1];
+            ix[ixPos++] = 0 | x0;
+            x2 += qupvt.adj43[rx2];
+            ix[ixPos++] = 0 | x1;
+            x3 += qupvt.adj43[rx3];
+            ix[ixPos++] = 0 | x2;
+            ix[ixPos++] = 0 | x3;
+        }
+        if (remaining != 0) {
+            var x0, x1;
+            var rx0, rx1;
+
+            x0 = xr[xrPos++] * istep;
+            x1 = xr[xrPos++] * istep;
+            rx0 = 0 | x0;
+            rx1 = 0 | x1;
+            x0 += qupvt.adj43[rx0];
+            x1 += qupvt.adj43[rx1];
+            ix[ixPos++] = 0 | x0;
+            ix[ixPos++] = 0 | x1;
+        }
+    }
+
+    /**
+     * Quantization function This function will select which lines to quantize
+     * and call the proper quantization function
+     */
+    function quantize_xrpow(xp, pi, istep, codInfo, prevNoise) {
+        /* quantize on xr^(3/4) instead of xr */
+        var sfb;
+        var sfbmax;
+        var j = 0;
+        var prev_data_use;
+        var accumulate = 0;
+        var accumulate01 = 0;
+        var xpPos = 0;
+        var iData = pi;
+        var iDataPos = 0;
+        var acc_iData = iData;
+        var acc_iDataPos = 0;
+        var acc_xp = xp;
+        var acc_xpPos = 0;
+
+        /*
+         * Reusing previously computed data does not seems to work if global
+         * gain is changed. Finding why it behaves this way would allow to use a
+         * cache of previously computed values (let's 10 cached values per sfb)
+         * that would probably provide a noticeable speedup
+         */
+        prev_data_use = (prevNoise != null && (codInfo.global_gain == prevNoise.global_gain));
+
+        if (codInfo.block_type == Encoder.SHORT_TYPE)
+            sfbmax = 38;
+        else
+            sfbmax = 21;
+
+        for (sfb = 0; sfb <= sfbmax; sfb++) {
+            var step = -1;
+
+            if (prev_data_use || codInfo.block_type == Encoder.NORM_TYPE) {
+                step = codInfo.global_gain
+                    - ((codInfo.scalefac[sfb] + (codInfo.preflag != 0 ? qupvt.pretab[sfb]
+                        : 0)) << (codInfo.scalefac_scale + 1))
+                    - codInfo.subblock_gain[codInfo.window[sfb]] * 8;
+            }
+            if (prev_data_use && (prevNoise.step[sfb] == step)) {
+                /*
+                 * do not recompute this part, but compute accumulated lines
+                 */
+                if (accumulate != 0) {
+                    quantize_lines_xrpow(accumulate, istep, acc_xp, acc_xpPos,
+                        acc_iData, acc_iDataPos);
+                    accumulate = 0;
+                }
+                if (accumulate01 != 0) {
+                    abort();//fix cc 精简
+                }
+            } else { /* should compute this part */
+                var l = codInfo.width[sfb];
+
+                if ((j + codInfo.width[sfb]) > codInfo.max_nonzero_coeff) {
+                    /* do not compute upper zero part */
+                    var usefullsize;
+                    usefullsize = codInfo.max_nonzero_coeff - j + 1;
+                    Arrays.fill(pi, codInfo.max_nonzero_coeff, 576, 0);
+                    l = usefullsize;
+
+                    if (l < 0) {
+                        l = 0;
+                    }
+
+                    /* no need to compute higher sfb values */
+                    sfb = sfbmax + 1;
+                }
+
+                /* accumulate lines to quantize */
+                if (0 == accumulate && 0 == accumulate01) {
+                    acc_iData = iData;
+                    acc_iDataPos = iDataPos;
+                    acc_xp = xp;
+                    acc_xpPos = xpPos;
+                }
+                if (prevNoise != null && prevNoise.sfb_count1 > 0
+                    && sfb >= prevNoise.sfb_count1
+                    && prevNoise.step[sfb] > 0
+                    && step >= prevNoise.step[sfb]) {
+
+                    if (accumulate != 0) {
+                        quantize_lines_xrpow(accumulate, istep, acc_xp,
+                            acc_xpPos, acc_iData, acc_iDataPos);
+                        accumulate = 0;
+                        acc_iData = iData;
+                        acc_iDataPos = iDataPos;
+                        acc_xp = xp;
+                        acc_xpPos = xpPos;
+                    }
+                    accumulate01 += l;
+                } else {
+                    if (accumulate01 != 0) {
+                        quantize_lines_xrpow_01(accumulate01, istep, acc_xp,
+                            acc_xpPos, acc_iData, acc_iDataPos);
+                        accumulate01 = 0;
+                        acc_iData = iData;
+                        acc_iDataPos = iDataPos;
+                        acc_xp = xp;
+                        acc_xpPos = xpPos;
+                    }
+                    accumulate += l;
+                }
+
+                if (l <= 0) {
+                    /*
+                     * rh: 20040215 may happen due to "prev_data_use"
+                     * optimization
+                     */
+                    if (accumulate01 != 0) {
+                        abort();//fix cc 精简
+                    }
+                    if (accumulate != 0) {
+                        abort();//fix cc 精简
+                    }
+
+                    break;
+                    /* ends for-loop */
+                }
+            }
+            if (sfb <= sfbmax) {
+                iDataPos += codInfo.width[sfb];
+                xpPos += codInfo.width[sfb];
+                j += codInfo.width[sfb];
+            }
+        }
+        if (accumulate != 0) { /* last data part */
+            quantize_lines_xrpow(accumulate, istep, acc_xp, acc_xpPos,
+                acc_iData, acc_iDataPos);
+            accumulate = 0;
+        }
+        if (accumulate01 != 0) { /* last data part */
+            abort();//fix cc 精简
+        }
+
+    }
+
+    /**
+     * ix_max
+     */
+    function ix_max(ix, ixPos, endPos) {
+        var max1 = 0, max2 = 0;
+
+        do {
+            var x1 = ix[ixPos++];
+            var x2 = ix[ixPos++];
+            if (max1 < x1)
+                max1 = x1;
+
+            if (max2 < x2)
+                max2 = x2;
+        } while (ixPos < endPos);
+        if (max1 < max2)
+            max1 = max2;
+        return max1;
+    }
+
+    function count_bit_ESC(ix, ixPos, end, t1, t2, s) {
+        /* ESC-table is used */
+        var linbits = Tables.ht[t1].xlen * 65536 + Tables.ht[t2].xlen;
+        var sum = 0, sum2;
+
+        do {
+            var x = ix[ixPos++];
+            var y = ix[ixPos++];
+
+            if (x != 0) {
+                if (x > 14) {
+                    x = 15;
+                    sum += linbits;
+                }
+                x *= 16;
+            }
+
+            if (y != 0) {
+                if (y > 14) {
+                    y = 15;
+                    sum += linbits;
+                }
+                x += y;
+            }
+
+            sum += Tables.largetbl[x];
+        } while (ixPos < end);
+
+        sum2 = sum & 0xffff;
+        sum >>= 16;
+
+        if (sum > sum2) {
+            sum = sum2;
+            t1 = t2;
+        }
+
+        s.bits += sum;
+        return t1;
+    }
+
+    function count_bit_noESC(ix, ixPos, end, s) {
+        /* No ESC-words */
+        var sum1 = 0;
+        var hlen1 = Tables.ht[1].hlen;
+
+        do {
+            var x = ix[ixPos + 0] * 2 + ix[ixPos + 1];
+            ixPos += 2;
+            sum1 += hlen1[x];
+        } while (ixPos < end);
+
+        s.bits += sum1;
+        return 1;
+    }
+
+    function count_bit_noESC_from2(ix, ixPos, end, t1, s) {
+        /* No ESC-words */
+        var sum = 0, sum2;
+        var xlen = Tables.ht[t1].xlen;
+        var hlen;
+        if (t1 == 2)
+            hlen = Tables.table23;
+        else
+            hlen = Tables.table56;
+
+        do {
+            var x = ix[ixPos + 0] * xlen + ix[ixPos + 1];
+            ixPos += 2;
+            sum += hlen[x];
+        } while (ixPos < end);
+
+        sum2 = sum & 0xffff;
+        sum >>= 16;
+
+        if (sum > sum2) {
+            sum = sum2;
+            t1++;
+        }
+
+        s.bits += sum;
+        return t1;
+    }
+
+    function count_bit_noESC_from3(ix, ixPos, end, t1, s) {
+        /* No ESC-words */
+        var sum1 = 0;
+        var sum2 = 0;
+        var sum3 = 0;
+        var xlen = Tables.ht[t1].xlen;
+        var hlen1 = Tables.ht[t1].hlen;
+        var hlen2 = Tables.ht[t1 + 1].hlen;
+        var hlen3 = Tables.ht[t1 + 2].hlen;
+
+        do {
+            var x = ix[ixPos + 0] * xlen + ix[ixPos + 1];
+            ixPos += 2;
+            sum1 += hlen1[x];
+            sum2 += hlen2[x];
+            sum3 += hlen3[x];
+        } while (ixPos < end);
+        var t = t1;
+        if (sum1 > sum2) {
+            sum1 = sum2;
+            t++;
+        }
+        if (sum1 > sum3) {
+            sum1 = sum3;
+            t = t1 + 2;
+        }
+        s.bits += sum1;
+
+        return t;
+    }
+
+    /*************************************************************************/
+    /* choose table */
+    /*************************************************************************/
+
+    var huf_tbl_noESC = [1, 2, 5, 7, 7, 10, 10, 13, 13,
+        13, 13, 13, 13, 13, 13];
+
+    /**
+     * Choose the Huffman table that will encode ix[begin..end] with the fewest
+     * bits.
+     *
+     * Note: This code contains knowledge about the sizes and characteristics of
+     * the Huffman tables as defined in the IS (Table B.7), and will not work
+     * with any arbitrary tables.
+     */
+    function choose_table(ix, ixPos, endPos, s) {
+        var max = ix_max(ix, ixPos, endPos);
+
+        switch (max) {
+            case 0:
+                return max;
+
+            case 1:
+                return count_bit_noESC(ix, ixPos, endPos, s);
+
+            case 2:
+            case 3:
+                return count_bit_noESC_from2(ix, ixPos, endPos,
+                    huf_tbl_noESC[max - 1], s);
+
+            case 4:
+            case 5:
+            case 6:
+            case 7:
+            case 8:
+            case 9:
+            case 10:
+            case 11:
+            case 12:
+            case 13:
+            case 14:
+            case 15:
+                return count_bit_noESC_from3(ix, ixPos, endPos,
+                    huf_tbl_noESC[max - 1], s);
+
+            default:
+                /* try tables with linbits */
+                if (max > QuantizePVT.IXMAX_VAL) {
+                    abort();//fix cc 精简
+                }
+                max -= 15;
+                var choice2;
+                for (choice2 = 24; choice2 < 32; choice2++) {
+                    if (Tables.ht[choice2].linmax >= max) {
+                        break;
+                    }
+                }
+                var choice;
+                for (choice = choice2 - 8; choice < 24; choice++) {
+                    if (Tables.ht[choice].linmax >= max) {
+                        break;
+                    }
+                }
+                return count_bit_ESC(ix, ixPos, endPos, choice, choice2, s);
+        }
+    }
+
+    /**
+     * count_bit
+     */
+    this.noquant_count_bits = function (gfc, gi, prev_noise) {
+        var ix = gi.l3_enc;
+        var i = Math.min(576, ((gi.max_nonzero_coeff + 2) >> 1) << 1);
+
+        if (prev_noise != null)
+            prev_noise.sfb_count1 = 0;
+
+        /* Determine count1 region */
+        for (; i > 1; i -= 2)
+            if ((ix[i - 1] | ix[i - 2]) != 0)
+                break;
+        gi.count1 = i;
+
+        /* Determines the number of bits to encode the quadruples. */
+        var a1 = 0;
+        var a2 = 0;
+        for (; i > 3; i -= 4) {
+            var p;
+            /* hack to check if all values <= 1 */
+            //throw "TODO: HACK         if ((((long) ix[i - 1] | (long) ix[i - 2] | (long) ix[i - 3] | (long) ix[i - 4]) & 0xffffffffL) > 1L        "
+            //if (true) {
+            if (((ix[i - 1] | ix[i - 2] | ix[i - 3] | ix[i - 4]) & 0x7fffffff) > 1) {
+                break;
+            }
+            p = ((ix[i - 4] * 2 + ix[i - 3]) * 2 + ix[i - 2]) * 2 + ix[i - 1];
+            a1 += Tables.t32l[p];
+            a2 += Tables.t33l[p];
+        }
+        var bits = a1;
+        gi.count1table_select = 0;
+        if (a1 > a2) {
+            bits = a2;
+            gi.count1table_select = 1;
+        }
+
+        gi.count1bits = bits;
+        gi.big_values = i;
+        if (i == 0)
+            return bits;
+
+        if (gi.block_type == Encoder.SHORT_TYPE) {
+            a1 = 3 * gfc.scalefac_band.s[3];
+            if (a1 > gi.big_values)
+                a1 = gi.big_values;
+            a2 = gi.big_values;
+
+        } else if (gi.block_type == Encoder.NORM_TYPE) {
+            /* bv_scf has 576 entries (0..575) */
+            a1 = gi.region0_count = gfc.bv_scf[i - 2];
+            a2 = gi.region1_count = gfc.bv_scf[i - 1];
+
+            a2 = gfc.scalefac_band.l[a1 + a2 + 2];
+            a1 = gfc.scalefac_band.l[a1 + 1];
+            if (a2 < i) {
+                var bi = new Bits(bits);
+                gi.table_select[2] = choose_table(ix, a2, i, bi);
+                bits = bi.bits;
+            }
+        } else {
+            gi.region0_count = 7;
+            /* gi.region1_count = SBPSY_l - 7 - 1; */
+            gi.region1_count = Encoder.SBMAX_l - 1 - 7 - 1;
+            a1 = gfc.scalefac_band.l[7 + 1];
+            a2 = i;
+            if (a1 > a2) {
+                a1 = a2;
+            }
+        }
+
+        /* have to allow for the case when bigvalues < region0 < region1 */
+        /* (and region0, region1 are ignored) */
+        a1 = Math.min(a1, i);
+        a2 = Math.min(a2, i);
+
+
+        /* Count the number of bits necessary to code the bigvalues region. */
+        if (0 < a1) {
+            var bi = new Bits(bits);
+            gi.table_select[0] = choose_table(ix, 0, a1, bi);
+            bits = bi.bits;
+        }
+        if (a1 < a2) {
+            var bi = new Bits(bits);
+            gi.table_select[1] = choose_table(ix, a1, a2, bi);
+            bits = bi.bits;
+        }
+        if (gfc.use_best_huffman == 2) {
+            abort();//fix cc 精简
+        }
+
+        if (prev_noise != null) {
+            if (gi.block_type == Encoder.NORM_TYPE) {
+                var sfb = 0;
+                while (gfc.scalefac_band.l[sfb] < gi.big_values) {
+                    sfb++;
+                }
+                prev_noise.sfb_count1 = sfb;
+            }
+        }
+
+        return bits;
+    }
+
+    this.count_bits = function (gfc, xr, gi, prev_noise) {
+        var ix = gi.l3_enc;
+
+        /* since quantize_xrpow uses table lookup, we need to check this first: */
+        var w = (QuantizePVT.IXMAX_VAL) / qupvt.IPOW20(gi.global_gain);
+
+        if (gi.xrpow_max > w)
+            return QuantizePVT.LARGE_BITS;
+
+        quantize_xrpow(xr, ix, qupvt.IPOW20(gi.global_gain), gi, prev_noise);
+
+        if ((gfc.substep_shaping & 2) != 0) {
+            abort();//fix cc 精简
+        }
+        return this.noquant_count_bits(gfc, gi, prev_noise);
+    }
+
+    /**
+     * re-calculate the best scalefac_compress using scfsi the saved bits are
+     * kept in the bit reservoir.
+     */
+    function recalc_divide_init(gfc, cod_info, ix, r01_bits, r01_div, r0_tbl, r1_tbl) {
+        var bigv = cod_info.big_values;
+
+        for (var r0 = 0; r0 <= 7 + 15; r0++) {
+            r01_bits[r0] = QuantizePVT.LARGE_BITS;
+        }
+
+        for (var r0 = 0; r0 < 16; r0++) {
+            var a1 = gfc.scalefac_band.l[r0 + 1];
+            if (a1 >= bigv)
+                break;
+            var r0bits = 0;
+            var bi = new Bits(r0bits);
+            var r0t = choose_table(ix, 0, a1, bi);
+            r0bits = bi.bits;
+
+            for (var r1 = 0; r1 < 8; r1++) {
+                var a2 = gfc.scalefac_band.l[r0 + r1 + 2];
+                if (a2 >= bigv)
+                    break;
+                var bits = r0bits;
+                bi = new Bits(bits);
+                var r1t = choose_table(ix, a1, a2, bi);
+                bits = bi.bits;
+                if (r01_bits[r0 + r1] > bits) {
+                    r01_bits[r0 + r1] = bits;
+                    r01_div[r0 + r1] = r0;
+                    r0_tbl[r0 + r1] = r0t;
+                    r1_tbl[r0 + r1] = r1t;
+                }
+            }
+        }
+    }
+
+    function recalc_divide_sub(gfc, cod_info2, gi, ix, r01_bits, r01_div, r0_tbl, r1_tbl) {
+        var bigv = cod_info2.big_values;
+
+        for (var r2 = 2; r2 < Encoder.SBMAX_l + 1; r2++) {
+            var a2 = gfc.scalefac_band.l[r2];
+            if (a2 >= bigv)
+                break;
+            var bits = r01_bits[r2 - 2] + cod_info2.count1bits;
+            if (gi.part2_3_length <= bits)
+                break;
+
+            var bi = new Bits(bits);
+            var r2t = choose_table(ix, a2, bigv, bi);
+            bits = bi.bits;
+            if (gi.part2_3_length <= bits)
+                continue;
+
+            gi.assign(cod_info2);
+            gi.part2_3_length = bits;
+            gi.region0_count = r01_div[r2 - 2];
+            gi.region1_count = r2 - 2 - r01_div[r2 - 2];
+            gi.table_select[0] = r0_tbl[r2 - 2];
+            gi.table_select[1] = r1_tbl[r2 - 2];
+            gi.table_select[2] = r2t;
+        }
+    }
+
+    this.best_huffman_divide = function (gfc, gi) {
+        var cod_info2 = new GrInfo();
+        var ix = gi.l3_enc;
+        var r01_bits = new_int(7 + 15 + 1);
+        var r01_div = new_int(7 + 15 + 1);
+        var r0_tbl = new_int(7 + 15 + 1);
+        var r1_tbl = new_int(7 + 15 + 1);
+
+        /* SHORT BLOCK stuff fails for MPEG2 */
+        if (gi.block_type == Encoder.SHORT_TYPE && gfc.mode_gr == 1)
+            return;
+
+        cod_info2.assign(gi);
+        if (gi.block_type == Encoder.NORM_TYPE) {
+            recalc_divide_init(gfc, gi, ix, r01_bits, r01_div, r0_tbl, r1_tbl);
+            recalc_divide_sub(gfc, cod_info2, gi, ix, r01_bits, r01_div,
+                r0_tbl, r1_tbl);
+        }
+        var i = cod_info2.big_values;
+        if (i == 0 || (ix[i - 2] | ix[i - 1]) > 1)
+            return;
+
+        i = gi.count1 + 2;
+        if (i > 576)
+            return;
+
+        /* Determines the number of bits to encode the quadruples. */
+        cod_info2.assign(gi);
+        cod_info2.count1 = i;
+        var a1 = 0;
+        var a2 = 0;
+
+
+        for (; i > cod_info2.big_values; i -= 4) {
+            var p = ((ix[i - 4] * 2 + ix[i - 3]) * 2 + ix[i - 2]) * 2
+                + ix[i - 1];
+            a1 += Tables.t32l[p];
+            a2 += Tables.t33l[p];
+        }
+        cod_info2.big_values = i;
+
+        cod_info2.count1table_select = 0;
+        if (a1 > a2) {
+            a1 = a2;
+            cod_info2.count1table_select = 1;
+        }
+
+        cod_info2.count1bits = a1;
+
+        if (cod_info2.block_type == Encoder.NORM_TYPE)
+            recalc_divide_sub(gfc, cod_info2, gi, ix, r01_bits, r01_div,
+                r0_tbl, r1_tbl);
+        else {
+            /* Count the number of bits necessary to code the bigvalues region. */
+            cod_info2.part2_3_length = a1;
+            a1 = gfc.scalefac_band.l[7 + 1];
+            if (a1 > i) {
+                a1 = i;
+            }
+            if (a1 > 0) {
+                var bi = new Bits(cod_info2.part2_3_length);
+                cod_info2.table_select[0] = choose_table(ix, 0, a1, bi);
+                cod_info2.part2_3_length = bi.bits;
+            }
+            if (i > a1) {
+                var bi = new Bits(cod_info2.part2_3_length);
+                cod_info2.table_select[1] = choose_table(ix, a1, i, bi);
+                cod_info2.part2_3_length = bi.bits;
+            }
+            if (gi.part2_3_length > cod_info2.part2_3_length)
+                gi.assign(cod_info2);
+        }
+    }
+
+    var slen1_n = [1, 1, 1, 1, 8, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16];
+    var slen2_n = [1, 2, 4, 8, 1, 2, 4, 8, 2, 4, 8, 2, 4, 8, 4, 8];
+    var slen1_tab = [0, 0, 0, 0, 3, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4];
+    var slen2_tab = [0, 1, 2, 3, 0, 1, 2, 3, 1, 2, 3, 1, 2, 3, 2, 3];
+    Takehiro.slen1_tab = slen1_tab;
+    Takehiro.slen2_tab = slen2_tab;
+
+    function scfsi_calc(ch, l3_side) {
+        var sfb;
+        var gi = l3_side.tt[1][ch];
+        var g0 = l3_side.tt[0][ch];
+
+        for (var i = 0; i < Tables.scfsi_band.length - 1; i++) {
+            for (sfb = Tables.scfsi_band[i]; sfb < Tables.scfsi_band[i + 1]; sfb++) {
+                if (g0.scalefac[sfb] != gi.scalefac[sfb]
+                    && gi.scalefac[sfb] >= 0)
+                    break;
+            }
+            if (sfb == Tables.scfsi_band[i + 1]) {
+                for (sfb = Tables.scfsi_band[i]; sfb < Tables.scfsi_band[i + 1]; sfb++) {
+                    gi.scalefac[sfb] = -1;
+                }
+                l3_side.scfsi[ch][i] = 1;
+            }
+        }
+        var s1 = 0;
+        var c1 = 0;
+        for (sfb = 0; sfb < 11; sfb++) {
+            if (gi.scalefac[sfb] == -1)
+                continue;
+            c1++;
+            if (s1 < gi.scalefac[sfb])
+                s1 = gi.scalefac[sfb];
+        }
+        var s2 = 0;
+        var c2 = 0;
+        for (; sfb < Encoder.SBPSY_l; sfb++) {
+            if (gi.scalefac[sfb] == -1)
+                continue;
+            c2++;
+            if (s2 < gi.scalefac[sfb])
+                s2 = gi.scalefac[sfb];
+        }
+
+        for (var i = 0; i < 16; i++) {
+            if (s1 < slen1_n[i] && s2 < slen2_n[i]) {
+                var c = slen1_tab[i] * c1 + slen2_tab[i] * c2;
+                if (gi.part2_length > c) {
+                    gi.part2_length = c;
+                    gi.scalefac_compress = i;
+                }
+            }
+        }
+    }
+
+    /**
+     * Find the optimal way to store the scalefactors. Only call this routine
+     * after final scalefactors have been chosen and the channel/granule will
+     * not be re-encoded.
+     */
+    this.best_scalefac_store = function (gfc, gr, ch, l3_side) {
+        /* use scalefac_scale if we can */
+        var gi = l3_side.tt[gr][ch];
+        var sfb, i, j, l;
+        var recalc = 0;
+
+        /*
+         * remove scalefacs from bands with ix=0. This idea comes from the AAC
+         * ISO docs. added mt 3/00
+         */
+        /* check if l3_enc=0 */
+        j = 0;
+        for (sfb = 0; sfb < gi.sfbmax; sfb++) {
+            var width = gi.width[sfb];
+            j += width;
+            for (l = -width; l < 0; l++) {
+                if (gi.l3_enc[l + j] != 0)
+                    break;
+            }
+            if (l == 0)
+                gi.scalefac[sfb] = recalc = -2;
+            /* anything goes. */
+            /*
+             * only best_scalefac_store and calc_scfsi know--and only they
+             * should know--about the magic number -2.
+             */
+        }
+
+        if (0 == gi.scalefac_scale && 0 == gi.preflag) {
+            var s = 0;
+            for (sfb = 0; sfb < gi.sfbmax; sfb++)
+                if (gi.scalefac[sfb] > 0)
+                    s |= gi.scalefac[sfb];
+
+            if (0 == (s & 1) && s != 0) {
+                for (sfb = 0; sfb < gi.sfbmax; sfb++)
+                    if (gi.scalefac[sfb] > 0)
+                        gi.scalefac[sfb] >>= 1;
+
+                gi.scalefac_scale = recalc = 1;
+            }
+        }
+
+        if (0 == gi.preflag && gi.block_type != Encoder.SHORT_TYPE
+            && gfc.mode_gr == 2) {
+            for (sfb = 11; sfb < Encoder.SBPSY_l; sfb++)
+                if (gi.scalefac[sfb] < qupvt.pretab[sfb]
+                    && gi.scalefac[sfb] != -2)
+                    break;
+            if (sfb == Encoder.SBPSY_l) {
+                for (sfb = 11; sfb < Encoder.SBPSY_l; sfb++)
+                    if (gi.scalefac[sfb] > 0)
+                        gi.scalefac[sfb] -= qupvt.pretab[sfb];
+
+                gi.preflag = recalc = 1;
+            }
+        }
+
+        for (i = 0; i < 4; i++)
+            l3_side.scfsi[ch][i] = 0;
+
+        if (gfc.mode_gr == 2 && gr == 1
+            && l3_side.tt[0][ch].block_type != Encoder.SHORT_TYPE
+            && l3_side.tt[1][ch].block_type != Encoder.SHORT_TYPE) {
+            scfsi_calc(ch, l3_side);
+            recalc = 0;
+        }
+        for (sfb = 0; sfb < gi.sfbmax; sfb++) {
+            if (gi.scalefac[sfb] == -2) {
+                gi.scalefac[sfb] = 0;
+                /* if anything goes, then 0 is a good choice */
+            }
+        }
+        if (recalc != 0) {
+            if (gfc.mode_gr == 2) {
+                this.scale_bitcount(gi);
+            } else {
+                this.scale_bitcount_lsf(gfc, gi);
+            }
+        }
+    }
+
+    //fix cc 精简
+
+    /**
+     * number of bits used to encode scalefacs.
+     *
+     * 18*slen1_tab[i] + 18*slen2_tab[i]
+     */
+    var scale_short = [0, 18, 36, 54, 54, 36, 54, 72,
+        54, 72, 90, 72, 90, 108, 108, 126];
+
+    /**
+     * number of bits used to encode scalefacs.
+     *
+     * 17*slen1_tab[i] + 18*slen2_tab[i]
+     */
+    var scale_mixed = [0, 18, 36, 54, 51, 35, 53, 71,
+        52, 70, 88, 69, 87, 105, 104, 122];
+
+    /**
+     * number of bits used to encode scalefacs.
+     *
+     * 11*slen1_tab[i] + 10*slen2_tab[i]
+     */
+    var scale_long = [0, 10, 20, 30, 33, 21, 31, 41, 32, 42,
+        52, 43, 53, 63, 64, 74];
+
+    /**
+     * Also calculates the number of bits necessary to code the scalefactors.
+     */
+    this.scale_bitcount = function (cod_info) {
+        var k, sfb, max_slen1 = 0, max_slen2 = 0;
+
+        /* maximum values */
+        var tab;
+        var scalefac = cod_info.scalefac;
+
+
+        if (cod_info.block_type == Encoder.SHORT_TYPE) {
+            tab = scale_short;
+            if (cod_info.mixed_block_flag != 0)
+                tab = scale_mixed;
+        } else { /* block_type == 1,2,or 3 */
+            tab = scale_long;
+            if (0 == cod_info.preflag) {
+                for (sfb = 11; sfb < Encoder.SBPSY_l; sfb++)
+                    if (scalefac[sfb] < qupvt.pretab[sfb])
+                        break;
+
+                if (sfb == Encoder.SBPSY_l) {
+                    cod_info.preflag = 1;
+                    for (sfb = 11; sfb < Encoder.SBPSY_l; sfb++)
+                        scalefac[sfb] -= qupvt.pretab[sfb];
+                }
+            }
+        }
+
+        for (sfb = 0; sfb < cod_info.sfbdivide; sfb++)
+            if (max_slen1 < scalefac[sfb])
+                max_slen1 = scalefac[sfb];
+
+        for (; sfb < cod_info.sfbmax; sfb++)
+            if (max_slen2 < scalefac[sfb])
+                max_slen2 = scalefac[sfb];
+
+        /*
+         * from Takehiro TOMINAGA <tominaga@isoternet.org> 10/99 loop over *all*
+         * posible values of scalefac_compress to find the one which uses the
+         * smallest number of bits. ISO would stop at first valid index
+         */
+        cod_info.part2_length = QuantizePVT.LARGE_BITS;
+        for (k = 0; k < 16; k++) {
+            if (max_slen1 < slen1_n[k] && max_slen2 < slen2_n[k]
+                && cod_info.part2_length > tab[k]) {
+                cod_info.part2_length = tab[k];
+                cod_info.scalefac_compress = k;
+            }
+        }
+        return cod_info.part2_length == QuantizePVT.LARGE_BITS;
+    }
+
+    /**
+     * table of largest scalefactor values for MPEG2
+     */
+    var max_range_sfac_tab = [[15, 15, 7, 7],
+        [15, 15, 7, 0], [7, 3, 0, 0], [15, 31, 31, 0],
+        [7, 7, 7, 0], [3, 3, 0, 0]];
+
+    /**
+     * Also counts the number of bits to encode the scalefacs but for MPEG 2
+     * Lower sampling frequencies (24, 22.05 and 16 kHz.)
+     *
+     * This is reverse-engineered from section 2.4.3.2 of the MPEG2 IS,
+     * "Audio Decoding Layer III"
+     */
+    this.scale_bitcount_lsf = function (gfc, cod_info) {
+        var table_number, row_in_table, partition, nr_sfb, window;
+        var over;
+        var i, sfb;
+        var max_sfac = new_int(4);
+//var partition_table;
+        var scalefac = cod_info.scalefac;
+
+        /*
+         * Set partition table. Note that should try to use table one, but do
+         * not yet...
+         */
+        if (cod_info.preflag != 0)
+            table_number = 2;
+        else
+            table_number = 0;
+
+        for (i = 0; i < 4; i++)
+            max_sfac[i] = 0;
+
+        if (cod_info.block_type == Encoder.SHORT_TYPE) {
+            row_in_table = 1;
+            var partition_table = qupvt.nr_of_sfb_block[table_number][row_in_table];
+            for (sfb = 0, partition = 0; partition < 4; partition++) {
+                nr_sfb = partition_table[partition] / 3;
+                for (i = 0; i < nr_sfb; i++, sfb++)
+                    for (window = 0; window < 3; window++)
+                        if (scalefac[sfb * 3 + window] > max_sfac[partition])
+                            max_sfac[partition] = scalefac[sfb * 3 + window];
+            }
+        } else {
+            row_in_table = 0;
+            var partition_table = qupvt.nr_of_sfb_block[table_number][row_in_table];
+            for (sfb = 0, partition = 0; partition < 4; partition++) {
+                nr_sfb = partition_table[partition];
+                for (i = 0; i < nr_sfb; i++, sfb++)
+                    if (scalefac[sfb] > max_sfac[partition])
+                        max_sfac[partition] = scalefac[sfb];
+            }
+        }
+
+        for (over = false, partition = 0; partition < 4; partition++) {
+            if (max_sfac[partition] > max_range_sfac_tab[table_number][partition])
+                over = true;
+        }
+        if (!over) {
+            var slen1, slen2, slen3, slen4;
+
+            cod_info.sfb_partition_table = qupvt.nr_of_sfb_block[table_number][row_in_table];
+            for (partition = 0; partition < 4; partition++)
+                cod_info.slen[partition] = log2tab[max_sfac[partition]];
+
+            /* set scalefac_compress */
+            slen1 = cod_info.slen[0];
+            slen2 = cod_info.slen[1];
+            slen3 = cod_info.slen[2];
+            slen4 = cod_info.slen[3];
+
+            switch (table_number) {
+                case 0:
+                    cod_info.scalefac_compress = (((slen1 * 5) + slen2) << 4)
+                        + (slen3 << 2) + slen4;
+                    break;
+
+                case 1:
+                    cod_info.scalefac_compress = 400 + (((slen1 * 5) + slen2) << 2)
+                        + slen3;
+                    break;
+
+                case 2:
+                    cod_info.scalefac_compress = 500 + (slen1 * 3) + slen2;
+                    break;
+
+                default:
+                    //fix cc 精简 print
+                    break;
+            }
+        }
+        if (!over) {
+            cod_info.part2_length = 0;
+            for (partition = 0; partition < 4; partition++)
+                cod_info.part2_length += cod_info.slen[partition]
+                    * cod_info.sfb_partition_table[partition];
+        }
+        return over;
+    }
+
+    /*
+     * Since no bands have been over-amplified, we can set scalefac_compress and
+     * slen[] for the formatter
+     */
+    var log2tab = [0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4,
+        4, 4, 4, 4];
+
+    this.huffman_init = function (gfc) {
+        for (var i = 2; i <= 576; i += 2) {
+            var scfb_anz = 0, bv_index;
+            while (gfc.scalefac_band.l[++scfb_anz] < i)
+                ;
+
+            bv_index = subdv_table[scfb_anz][0]; // .region0_count
+            while (gfc.scalefac_band.l[bv_index + 1] > i)
+                bv_index--;
+
+            if (bv_index < 0) {
+                /*
+                 * this is an indication that everything is going to be encoded
+                 * as region0: bigvalues < region0 < region1 so lets set
+                 * region0, region1 to some value larger than bigvalues
+                 */
+                bv_index = subdv_table[scfb_anz][0]; // .region0_count
+            }
+
+            gfc.bv_scf[i - 2] = bv_index;
+
+            bv_index = subdv_table[scfb_anz][1]; // .region1_count
+            while (gfc.scalefac_band.l[bv_index + gfc.bv_scf[i - 2] + 2] > i)
+                bv_index--;
+
+            if (bv_index < 0) {
+                bv_index = subdv_table[scfb_anz][1]; // .region1_count
+            }
+
+            gfc.bv_scf[i - 1] = bv_index;
+        }
+    }
+}
+
+/*
+ *  ReplayGainAnalysis - analyzes input samples and give the recommended dB change
+ *  Copyright (C) 2001 David Robinson and Glen Sawyer
+ *  Improvements and optimizations added by Frank Klemm, and by Marcel Muller 
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *  concept and filter values by David Robinson (David@Robinson.org)
+ *    -- blame him if you think the idea is flawed
+ *  original coding by Glen Sawyer (mp3gain@hotmail.com)
+ *    -- blame him if you think this runs too slowly, or the coding is otherwise flawed
+ *
+ *  lots of code improvements by Frank Klemm ( http://www.uni-jena.de/~pfk/mpp/ )
+ *    -- credit him for all the _good_ programming ;)
+ *
+ *
+ *  For an explanation of the concepts and the basic algorithms involved, go to:
+ *    http://www.replaygain.org/
+ */
+
+/*
+ *  Here's the deal. Call
+ *
+ *    InitGainAnalysis ( long samplefreq );
+ *
+ *  to initialize everything. Call
+ *
+ *    AnalyzeSamples ( var Float_t*  left_samples,
+ *                     var Float_t*  right_samples,
+ *                     size_t          num_samples,
+ *                     int             num_channels );
+ *
+ *  as many times as you want, with as many or as few samples as you want.
+ *  If mono, pass the sample buffer in through left_samples, leave
+ *  right_samples NULL, and make sure num_channels = 1.
+ *
+ *    GetTitleGain()
+ *
+ *  will return the recommended dB level change for all samples analyzed
+ *  SINCE THE LAST TIME you called GetTitleGain() OR InitGainAnalysis().
+ *
+ *    GetAlbumGain()
+ *
+ *  will return the recommended dB level change for all samples analyzed
+ *  since InitGainAnalysis() was called and finalized with GetTitleGain().
+ *
+ *  Pseudo-code to process an album:
+ *
+ *    Float_t       l_samples [4096];
+ *    Float_t       r_samples [4096];
+ *    size_t        num_samples;
+ *    unsigned int  num_songs;
+ *    unsigned int  i;
+ *
+ *    InitGainAnalysis ( 44100 );
+ *    for ( i = 1; i <= num_songs; i++ ) {
+ *        while ( ( num_samples = getSongSamples ( song[i], left_samples, right_samples ) ) > 0 )
+ *            AnalyzeSamples ( left_samples, right_samples, num_samples, 2 );
+ *        fprintf ("Recommended dB change for song %2d: %+6.2 dB\n", i, GetTitleGain() );
+ *    }
+ *    fprintf ("Recommended dB change for whole album: %+6.2 dB\n", GetAlbumGain() );
+ */
+
+/*
+ *  So here's the main source of potential code confusion:
+ *
+ *  The filters applied to the incoming samples are IIR filters,
+ *  meaning they rely on up to <filter order> number of previous samples
+ *  AND up to <filter order> number of previous filtered samples.
+ *
+ *  I set up the AnalyzeSamples routine to minimize memory usage and interface
+ *  complexity. The speed isn't compromised too much (I don't think), but the
+ *  internal complexity is higher than it should be for such a relatively
+ *  simple routine.
+ *
+ *  Optimization/clarity suggestions are welcome.
+ */
+
+/**
+ * Table entries per dB
+ */
+GainAnalysis.STEPS_per_dB = 100.;
+/**
+ * Table entries for 0...MAX_dB (normal max. values are 70...80 dB)
+ */
+GainAnalysis.MAX_dB = 120.;
+GainAnalysis.GAIN_NOT_ENOUGH_SAMPLES = -24601;
+GainAnalysis.GAIN_ANALYSIS_ERROR = 0;
+GainAnalysis.GAIN_ANALYSIS_OK = 1;
+GainAnalysis.INIT_GAIN_ANALYSIS_ERROR = 0;
+GainAnalysis.INIT_GAIN_ANALYSIS_OK = 1;
+
+GainAnalysis.YULE_ORDER = 10;
+GainAnalysis.MAX_ORDER = GainAnalysis.YULE_ORDER;
+
+GainAnalysis.MAX_SAMP_FREQ = 48000;
+GainAnalysis.RMS_WINDOW_TIME_NUMERATOR = 1;
+GainAnalysis.RMS_WINDOW_TIME_DENOMINATOR = 20;
+GainAnalysis.MAX_SAMPLES_PER_WINDOW = ((GainAnalysis.MAX_SAMP_FREQ * GainAnalysis.RMS_WINDOW_TIME_NUMERATOR) / GainAnalysis.RMS_WINDOW_TIME_DENOMINATOR + 1);
+
+function GainAnalysis() {
+	//fix 精简
+}
+
+
+function Presets() {
+    //fix 精简
+
+    function ABRPresets(kbps, comp, compS,
+                        joint, fix, shThreshold,
+                        shThresholdS, bass, sc,
+                        mask, lower, curve,
+                        interCh, sfScale) {
+        this.quant_comp = comp;
+        this.quant_comp_s = compS;
+        this.safejoint = joint;
+        this.nsmsfix = fix;
+        this.st_lrm = shThreshold;
+        this.st_s = shThresholdS;
+        this.nsbass = bass;
+        this.scale = sc;
+        this.masking_adj = mask;
+        this.ath_lower = lower;
+        this.ath_curve = curve;
+        this.interch = interCh;
+        this.sfscale = sfScale;
+    }
+
+    var lame;
+
+    this.setModules = function (_lame) {
+        lame = _lame;
+    };
+
+	//fix cc 精简
+    function apply_vbr_preset(gfp, a, enforce) {
+        abort();//fix cc 精简
+    }
+
+    /**
+     * <PRE>
+     *  Switch mappings for ABR mode
+     *
+     *              kbps  quant q_s safejoint nsmsfix st_lrm  st_s  ns-bass scale   msk ath_lwr ath_curve  interch , sfscale
+     * </PRE>
+     */
+    var abr_switch_map = [
+        new ABRPresets(8, 9, 9, 0, 0, 6.60, 145, 0, 0.95, 0, -30.0, 11, 0.0012, 1), /*   8, impossible to use in stereo */
+        new ABRPresets(16, 9, 9, 0, 0, 6.60, 145, 0, 0.95, 0, -25.0, 11, 0.0010, 1), /*  16 */
+        new ABRPresets(24, 9, 9, 0, 0, 6.60, 145, 0, 0.95, 0, -20.0, 11, 0.0010, 1), /*  24 */
+        new ABRPresets(32, 9, 9, 0, 0, 6.60, 145, 0, 0.95, 0, -15.0, 11, 0.0010, 1), /*  32 */
+        new ABRPresets(40, 9, 9, 0, 0, 6.60, 145, 0, 0.95, 0, -10.0, 11, 0.0009, 1), /*  40 */
+        new ABRPresets(48, 9, 9, 0, 0, 6.60, 145, 0, 0.95, 0, -10.0, 11, 0.0009, 1), /*  48 */
+        new ABRPresets(56, 9, 9, 0, 0, 6.60, 145, 0, 0.95, 0, -6.0, 11, 0.0008, 1), /*  56 */
+        new ABRPresets(64, 9, 9, 0, 0, 6.60, 145, 0, 0.95, 0, -2.0, 11, 0.0008, 1), /*  64 */
+        new ABRPresets(80, 9, 9, 0, 0, 6.60, 145, 0, 0.95, 0, .0, 8, 0.0007, 1), /*  80 */
+        new ABRPresets(96, 9, 9, 0, 2.50, 6.60, 145, 0, 0.95, 0, 1.0, 5.5, 0.0006, 1), /*  96 */
+        new ABRPresets(112, 9, 9, 0, 2.25, 6.60, 145, 0, 0.95, 0, 2.0, 4.5, 0.0005, 1), /* 112 */
+        new ABRPresets(128, 9, 9, 0, 1.95, 6.40, 140, 0, 0.95, 0, 3.0, 4, 0.0002, 1), /* 128 */
+        new ABRPresets(160, 9, 9, 1, 1.79, 6.00, 135, 0, 0.95, -2, 5.0, 3.5, 0, 1), /* 160 */
+        new ABRPresets(192, 9, 9, 1, 1.49, 5.60, 125, 0, 0.97, -4, 7.0, 3, 0, 0), /* 192 */
+        new ABRPresets(224, 9, 9, 1, 1.25, 5.20, 125, 0, 0.98, -6, 9.0, 2, 0, 0), /* 224 */
+        new ABRPresets(256, 9, 9, 1, 0.97, 5.20, 125, 0, 1.00, -8, 10.0, 1, 0, 0), /* 256 */
+        new ABRPresets(320, 9, 9, 1, 0.90, 5.20, 125, 0, 1.00, -10, 12.0, 0, 0, 0)  /* 320 */
+    ];
+
+    function apply_abr_preset(gfp, preset, enforce) {
+        /* Variables for the ABR stuff */
+        var actual_bitrate = preset;
+
+        var r = lame.nearestBitrateFullIndex(preset);
+
+        gfp.VBR = VbrMode.vbr_abr;
+        gfp.VBR_mean_bitrate_kbps = actual_bitrate;
+        gfp.VBR_mean_bitrate_kbps = Math.min(gfp.VBR_mean_bitrate_kbps, 320);
+        gfp.VBR_mean_bitrate_kbps = Math.max(gfp.VBR_mean_bitrate_kbps, 8);
+        gfp.brate = gfp.VBR_mean_bitrate_kbps;
+        if (gfp.VBR_mean_bitrate_kbps > 320) {
+            gfp.disable_reservoir = true;
+        }
+
+        /* parameters for which there is no proper set/get interface */
+        if (abr_switch_map[r].safejoint > 0)
+            gfp.exp_nspsytune = gfp.exp_nspsytune | 2;
+        /* safejoint */
+
+        if (abr_switch_map[r].sfscale > 0) {
+            gfp.internal_flags.noise_shaping = 2;
+        }
+        /* ns-bass tweaks */
+        if (Math.abs(abr_switch_map[r].nsbass) > 0) {
+            var k = (int)(abr_switch_map[r].nsbass * 4);
+            if (k < 0)
+                k += 64;
+            gfp.exp_nspsytune = gfp.exp_nspsytune | (k << 2);
+        }
+
+        if (enforce != 0)
+            gfp.quant_comp = abr_switch_map[r].quant_comp;
+        else if (!(Math.abs(gfp.quant_comp - -1) > 0))
+            gfp.quant_comp = abr_switch_map[r].quant_comp;
+        // SET_OPTION(quant_comp, abr_switch_map[r].quant_comp, -1);
+        if (enforce != 0)
+            gfp.quant_comp_short = abr_switch_map[r].quant_comp_s;
+        else if (!(Math.abs(gfp.quant_comp_short - -1) > 0))
+            gfp.quant_comp_short = abr_switch_map[r].quant_comp_s;
+        // SET_OPTION(quant_comp_short, abr_switch_map[r].quant_comp_s, -1);
+
+        if (enforce != 0)
+            gfp.msfix = abr_switch_map[r].nsmsfix;
+        else if (!(Math.abs(gfp.msfix - -1) > 0))
+            gfp.msfix = abr_switch_map[r].nsmsfix;
+        // SET_OPTION(msfix, abr_switch_map[r].nsmsfix, -1);
+
+        if (enforce != 0)
+            gfp.internal_flags.nsPsy.attackthre = abr_switch_map[r].st_lrm;
+        else if (!(Math.abs(gfp.internal_flags.nsPsy.attackthre - -1) > 0))
+            gfp.internal_flags.nsPsy.attackthre = abr_switch_map[r].st_lrm;
+        // SET_OPTION(short_threshold_lrm, abr_switch_map[r].st_lrm, -1);
+        if (enforce != 0)
+            gfp.internal_flags.nsPsy.attackthre_s = abr_switch_map[r].st_s;
+        else if (!(Math.abs(gfp.internal_flags.nsPsy.attackthre_s - -1) > 0))
+            gfp.internal_flags.nsPsy.attackthre_s = abr_switch_map[r].st_s;
+        // SET_OPTION(short_threshold_s, abr_switch_map[r].st_s, -1);
+
+        /*
+         * ABR seems to have big problems with clipping, especially at low
+         * bitrates
+         */
+        /*
+         * so we compensate for that here by using a scale value depending on
+         * bitrate
+         */
+        if (enforce != 0)
+            gfp.scale = abr_switch_map[r].scale;
+        else if (!(Math.abs(gfp.scale - -1) > 0))
+            gfp.scale = abr_switch_map[r].scale;
+        // SET_OPTION(scale, abr_switch_map[r].scale, -1);
+
+        if (enforce != 0)
+            gfp.maskingadjust = abr_switch_map[r].masking_adj;
+        else if (!(Math.abs(gfp.maskingadjust - 0) > 0))
+            gfp.maskingadjust = abr_switch_map[r].masking_adj;
+        // SET_OPTION(maskingadjust, abr_switch_map[r].masking_adj, 0);
+        if (abr_switch_map[r].masking_adj > 0) {
+            if (enforce != 0)
+                gfp.maskingadjust_short = (abr_switch_map[r].masking_adj * .9);
+            else if (!(Math.abs(gfp.maskingadjust_short - 0) > 0))
+                gfp.maskingadjust_short = (abr_switch_map[r].masking_adj * .9);
+            // SET_OPTION(maskingadjust_short, abr_switch_map[r].masking_adj *
+            // .9, 0);
+        } else {
+            if (enforce != 0)
+                gfp.maskingadjust_short = (abr_switch_map[r].masking_adj * 1.1);
+            else if (!(Math.abs(gfp.maskingadjust_short - 0) > 0))
+                gfp.maskingadjust_short = (abr_switch_map[r].masking_adj * 1.1);
+            // SET_OPTION(maskingadjust_short, abr_switch_map[r].masking_adj *
+            // 1.1, 0);
+        }
+
+        if (enforce != 0)
+            gfp.ATHlower = -abr_switch_map[r].ath_lower / 10.;
+        else if (!(Math.abs((-gfp.ATHlower * 10.) - 0) > 0))
+            gfp.ATHlower = -abr_switch_map[r].ath_lower / 10.;
+        // SET_OPTION(ATHlower, abr_switch_map[r].ath_lower, 0);
+        if (enforce != 0)
+            gfp.ATHcurve = abr_switch_map[r].ath_curve;
+        else if (!(Math.abs(gfp.ATHcurve - -1) > 0))
+            gfp.ATHcurve = abr_switch_map[r].ath_curve;
+        // SET_OPTION(ATHcurve, abr_switch_map[r].ath_curve, -1);
+
+        if (enforce != 0)
+            gfp.interChRatio = abr_switch_map[r].interch;
+        else if (!(Math.abs(gfp.interChRatio - -1) > 0))
+            gfp.interChRatio = abr_switch_map[r].interch;
+        // SET_OPTION(interChRatio, abr_switch_map[r].interch, -1);
+
+        return preset;
+    }
+
+    this.apply_preset = function(gfp, preset, enforce) {
+        /* translate legacy presets */
+        switch (preset) {
+            case Lame.R3MIX:
+            {
+                preset = Lame.V3;
+                gfp.VBR = VbrMode.vbr_mtrh;
+                break;
+            }
+            case Lame.MEDIUM:
+            {
+                preset = Lame.V4;
+                gfp.VBR = VbrMode.vbr_rh;
+                break;
+            }
+            case Lame.MEDIUM_FAST:
+            {
+                preset = Lame.V4;
+                gfp.VBR = VbrMode.vbr_mtrh;
+                break;
+            }
+            case Lame.STANDARD:
+            {
+                preset = Lame.V2;
+                gfp.VBR = VbrMode.vbr_rh;
+                break;
+            }
+            case Lame.STANDARD_FAST:
+            {
+                preset = Lame.V2;
+                gfp.VBR = VbrMode.vbr_mtrh;
+                break;
+            }
+            case Lame.EXTREME:
+            {
+                preset = Lame.V0;
+                gfp.VBR = VbrMode.vbr_rh;
+                break;
+            }
+            case Lame.EXTREME_FAST:
+            {
+                preset = Lame.V0;
+                gfp.VBR = VbrMode.vbr_mtrh;
+                break;
+            }
+            case Lame.INSANE:
+            {
+                preset = 320;
+                gfp.preset = preset;
+                apply_abr_preset(gfp, preset, enforce);
+                gfp.VBR = VbrMode.vbr_off;
+                return preset;
+            }
+        }
+
+        gfp.preset = preset;
+        {
+            switch (preset) {
+                case Lame.V9:
+                    apply_vbr_preset(gfp, 9, enforce);
+                    return preset;
+                case Lame.V8:
+                    apply_vbr_preset(gfp, 8, enforce);
+                    return preset;
+                case Lame.V7:
+                    apply_vbr_preset(gfp, 7, enforce);
+                    return preset;
+                case Lame.V6:
+                    apply_vbr_preset(gfp, 6, enforce);
+                    return preset;
+                case Lame.V5:
+                    apply_vbr_preset(gfp, 5, enforce);
+                    return preset;
+                case Lame.V4:
+                    apply_vbr_preset(gfp, 4, enforce);
+                    return preset;
+                case Lame.V3:
+                    apply_vbr_preset(gfp, 3, enforce);
+                    return preset;
+                case Lame.V2:
+                    apply_vbr_preset(gfp, 2, enforce);
+                    return preset;
+                case Lame.V1:
+                    apply_vbr_preset(gfp, 1, enforce);
+                    return preset;
+                case Lame.V0:
+                    apply_vbr_preset(gfp, 0, enforce);
+                    return preset;
+                default:
+                    break;
+            }
+        }
+        if (8 <= preset && preset <= 320) {
+            return apply_abr_preset(gfp, preset, enforce);
+        }
+
+        /* no corresponding preset found */
+        gfp.preset = 0;
+        return preset;
+    }
+
+    // Rest from getset.c:
+
+    //fix cc 精简
+
+}
+
+/*
+ *      bit reservoir source file
+ *
+ *      Copyright (c) 1999-2000 Mark Taylor
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* $Id: Reservoir.java,v 1.9 2011/05/24 20:48:06 kenchis Exp $ */
+
+//package mp3;
+
+/**
+ * ResvFrameBegin:<BR>
+ * Called (repeatedly) at the beginning of a frame. Updates the maximum size of
+ * the reservoir, and checks to make sure main_data_begin was set properly by
+ * the formatter<BR>
+ * Background information:
+ * 
+ * This is the original text from the ISO standard. Because of sooo many bugs
+ * and irritations correcting comments are added in brackets []. A '^W' means
+ * you should remove the last word.
+ * 
+ * <PRE>
+ *  1. The following rule can be used to calculate the maximum
+ *     number of bits used for one granule [^W frame]:<BR>
+ *     At the highest possible bitrate of Layer III (320 kbps
+ *     per stereo signal [^W^W^W], 48 kHz) the frames must be of
+ *     [^W^W^W are designed to have] constant length, i.e.
+ *     one buffer [^W^W the frame] length is:<BR>
+ * 
+ *         320 kbps * 1152/48 kHz = 7680 bit = 960 byte
+ * 
+ *     This value is used as the maximum buffer per channel [^W^W] at
+ *     lower bitrates [than 320 kbps]. At 64 kbps mono or 128 kbps
+ *     stereo the main granule length is 64 kbps * 576/48 kHz = 768 bit
+ *     [per granule and channel] at 48 kHz sampling frequency.
+ *     This means that there is a maximum deviation (short time buffer
+ *     [= reservoir]) of 7680 - 2*2*768 = 4608 bits is allowed at 64 kbps.
+ *     The actual deviation is equal to the number of bytes [with the
+ *     meaning of octets] denoted by the main_data_end offset pointer.
+ *     The actual maximum deviation is (2^9-1)*8 bit = 4088 bits
+ *     [for MPEG-1 and (2^8-1)*8 bit for MPEG-2, both are hard limits].
+ *     ... The xchange of buffer bits between the left and right channel
+ *     is allowed without restrictions [exception: dual channel].
+ *     Because of the [constructed] constraint on the buffer size
+ *     main_data_end is always set to 0 in the case of bit_rate_index==14,
+ *     i.e. data rate 320 kbps per stereo signal [^W^W^W]. In this case
+ *     all data are allocated between adjacent header [^W sync] words
+ *     [, i.e. there is no buffering at all].
+ * </PRE>
+ */
+
+
+function Reservoir() {
+	var bs;
+
+	this.setModules  = function(_bs) {
+		bs = _bs;
+	}
+
+	this.ResvFrameBegin = function(gfp, mean_bits) {
+		var gfc = gfp.internal_flags;
+		var maxmp3buf;
+		var l3_side = gfc.l3_side;
+
+		var frameLength = bs.getframebits(gfp);
+		mean_bits.bits = (frameLength - gfc.sideinfo_len * 8) / gfc.mode_gr;
+
+		/**
+		 * <PRE>
+		 *  Meaning of the variables:
+		 *      resvLimit: (0, 8, ..., 8*255 (MPEG-2), 8*511 (MPEG-1))
+		 *          Number of bits can be stored in previous frame(s) due to
+		 *          counter size constaints
+		 *      maxmp3buf: ( ??? ... 8*1951 (MPEG-1 and 2), 8*2047 (MPEG-2.5))
+		 *          Number of bits allowed to encode one frame (you can take 8*511 bit
+		 *          from the bit reservoir and at most 8*1440 bit from the current
+		 *          frame (320 kbps, 32 kHz), so 8*1951 bit is the largest possible
+		 *          value for MPEG-1 and -2)
+		 * 
+		 *          maximum allowed granule/channel size times 4 = 8*2047 bits.,
+		 *          so this is the absolute maximum supported by the format.
+		 * 
+		 * 
+		 *      fullFrameBits:  maximum number of bits available for encoding
+		 *                      the current frame.
+		 * 
+		 *      mean_bits:      target number of bits per granule.
+		 * 
+		 *      frameLength:
+		 * 
+		 *      gfc.ResvMax:   maximum allowed reservoir
+		 * 
+		 *      gfc.ResvSize:  current reservoir size
+		 * 
+		 *      l3_side.resvDrain_pre:
+		 *         ancillary data to be added to previous frame:
+		 *         (only usefull in VBR modes if it is possible to have
+		 *         maxmp3buf < fullFrameBits)).  Currently disabled,
+		 *         see #define NEW_DRAIN
+		 *         2010-02-13: RH now enabled, it seems to be needed for CBR too,
+		 *                     as there exists one example, where the FhG decoder
+		 *                     can't decode a -b320 CBR file anymore.
+		 * 
+		 *      l3_side.resvDrain_post:
+		 *         ancillary data to be added to this frame:
+		 * 
+		 * </PRE>
+		 */
+
+		/* main_data_begin has 9 bits in MPEG-1, 8 bits MPEG-2 */
+		var resvLimit = (8 * 256) * gfc.mode_gr - 8;
+
+		/*
+		 * maximum allowed frame size. dont use more than this number of bits,
+		 * even if the frame has the space for them:
+		 */
+		if (gfp.brate > 320) {
+			abort();//fix cc 精简
+		} else {
+			/*
+			 * all mp3 decoders should have enough buffer to handle this value:
+			 * size of a 320kbps 32kHz frame
+			 */
+			maxmp3buf = 8 * 1440;
+
+			/*
+			 * Bouvigne suggests this more lax interpretation of the ISO doc
+			 * instead of using 8*960.
+			 */
+
+			if (gfp.strict_ISO) {
+				abort();//fix cc 精简
+			}
+		}
+
+		gfc.ResvMax = maxmp3buf - frameLength;
+		if (gfc.ResvMax > resvLimit)
+			gfc.ResvMax = resvLimit;
+		if (gfc.ResvMax < 0 || gfp.disable_reservoir)
+			gfc.ResvMax = 0;
+
+		var fullFrameBits = mean_bits.bits * gfc.mode_gr
+				+ Math.min(gfc.ResvSize, gfc.ResvMax);
+
+		if (fullFrameBits > maxmp3buf)
+			fullFrameBits = maxmp3buf;
+
+
+		l3_side.resvDrain_pre = 0;
+
+		// frame analyzer code
+		if (gfc.pinfo != null) {
+			abort();//fix cc 精简
+		}
+
+		return fullFrameBits;
+	}
+
+	/**
+	 * returns targ_bits: target number of bits to use for 1 granule<BR>
+	 * extra_bits: amount extra available from reservoir<BR>
+	 * Mark Taylor 4/99
+	 */
+	this.ResvMaxBits = function(gfp, mean_bits, targ_bits, cbr) {
+		var gfc = gfp.internal_flags;
+		var add_bits;
+        var ResvSize = gfc.ResvSize, ResvMax = gfc.ResvMax;
+
+		/* compensate the saved bits used in the 1st granule */
+		if (cbr != 0)
+			ResvSize += mean_bits;
+
+		if ((gfc.substep_shaping & 1) != 0)
+			ResvMax *= 0.9;
+
+		targ_bits.bits = mean_bits;
+
+		/* extra bits if the reservoir is almost full */
+		if (ResvSize * 10 > ResvMax * 9) {
+			add_bits = ResvSize - (ResvMax * 9) / 10;
+			targ_bits.bits += add_bits;
+			gfc.substep_shaping |= 0x80;
+		} else {
+			add_bits = 0;
+			gfc.substep_shaping &= 0x7f;
+			/*
+			 * build up reservoir. this builds the reservoir a little slower
+			 * than FhG. It could simple be mean_bits/15, but this was rigged to
+			 * always produce 100 (the old value) at 128kbs
+			 */
+			if (!gfp.disable_reservoir && 0 == (gfc.substep_shaping & 1))
+				targ_bits.bits -= .1 * mean_bits;
+		}
+
+		/* amount from the reservoir we are allowed to use. ISO says 6/10 */
+		var extra_bits = (ResvSize < (gfc.ResvMax * 6) / 10 ? ResvSize
+				: (gfc.ResvMax * 6) / 10);
+		extra_bits -= add_bits;
+
+		if (extra_bits < 0)
+			extra_bits = 0;
+		return extra_bits;
+	}
+
+	/**
+	 * Called after a granule's bit allocation. Readjusts the size of the
+	 * reservoir to reflect the granule's usage.
+	 */
+	this.ResvAdjust = function(gfc, gi) {
+		gfc.ResvSize -= gi.part2_3_length + gi.part2_length;
+	}
+
+	/**
+	 * Called after all granules in a frame have been allocated. Makes sure that
+	 * the reservoir size is within limits, possibly by adding stuffing bits.
+	 */
+	this.ResvFrameEnd = function(gfc, mean_bits) {
+		var over_bits;
+		var l3_side = gfc.l3_side;
+
+		gfc.ResvSize += mean_bits * gfc.mode_gr;
+		var stuffingBits = 0;
+		l3_side.resvDrain_post = 0;
+		l3_side.resvDrain_pre = 0;
+
+		/* we must be byte aligned */
+		if ((over_bits = gfc.ResvSize % 8) != 0)
+			stuffingBits += over_bits;
+
+		over_bits = (gfc.ResvSize - stuffingBits) - gfc.ResvMax;
+		if (over_bits > 0) {
+			stuffingBits += over_bits;
+		}
+
+		/*
+		 * NOTE: enabling the NEW_DRAIN code fixes some problems with FhG
+		 * decoder shipped with MS Windows operating systems. Using this, it is
+		 * even possible to use Gabriel's lax buffer consideration again, which
+		 * assumes, any decoder should have a buffer large enough for a 320 kbps
+		 * frame at 32 kHz sample rate.
+		 * 
+		 * old drain code: lame -b320 BlackBird.wav --. does not play with
+		 * GraphEdit.exe using FhG decoder V1.5 Build 50
+		 * 
+		 * new drain code: lame -b320 BlackBird.wav --. plays fine with
+		 * GraphEdit.exe using FhG decoder V1.5 Build 50
+		 * 
+		 * Robert Hegemann, 2010-02-13.
+		 */
+		/*
+		 * drain as many bits as possible into previous frame ancillary data In
+		 * particular, in VBR mode ResvMax may have changed, and we have to make
+		 * sure main_data_begin does not create a reservoir bigger than ResvMax
+		 * mt 4/00
+		 */
+		{
+			var mdb_bytes = Math.min(l3_side.main_data_begin * 8, stuffingBits) / 8;
+			l3_side.resvDrain_pre += 8 * mdb_bytes;
+			stuffingBits -= 8 * mdb_bytes;
+			gfc.ResvSize -= 8 * mdb_bytes;
+			l3_side.main_data_begin -= mdb_bytes;
+		}
+		/* drain the rest into this frames ancillary data */
+		l3_side.resvDrain_post += stuffingBits;
+		gfc.ResvSize -= stuffingBits;
+	}
+}
+
+
+/**
+ * A Vbr header may be present in the ancillary data field of the first frame of
+ * an mp3 bitstream<BR>
+ * The Vbr header (optionally) contains
+ * <UL>
+ * <LI>frames total number of audio frames in the bitstream
+ * <LI>bytes total number of bytes in the bitstream
+ * <LI>toc table of contents
+ * </UL>
+ *
+ * toc (table of contents) gives seek points for random access.<BR>
+ * The ith entry determines the seek point for i-percent duration.<BR>
+ * seek point in bytes = (toc[i]/256.0) * total_bitstream_bytes<BR>
+ * e.g. half duration seek point = (toc[50]/256.0) * total_bitstream_bytes
+ */
+VBRTag.NUMTOCENTRIES = 100;
+VBRTag.MAXFRAMESIZE = 2880;
+
+function VBRTag() {
+
+    var lame;
+    var bs;
+    var v;
+
+    this.setModules = function (_lame, _bs, _v) {
+        lame = _lame;
+        bs = _bs;
+        v = _v;
+    };
+	
+	//fix 精简
+	
+    /**
+     * Lookup table for fast CRC-16 computation. Uses the polynomial
+     * x^16+x^15+x^2+1
+     */
+    var crc16Lookup = [0x0000, 0xC0C1, 0xC181, 0x0140,
+        0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741,
+        0x0500, 0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41,
+        0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1, 0xCB81, 0x0B40,
+        0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941,
+        0x1B00, 0xDBC1, 0xDA81, 0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40,
+        0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540,
+        0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341,
+        0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001, 0x30C0, 0x3180, 0xF141,
+        0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740,
+        0xF501, 0x35C0, 0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40,
+        0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80, 0xFB41,
+        0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940,
+        0xEB01, 0x2BC0, 0x2A80, 0xEA41, 0xEE01, 0x2EC0, 0x2F80, 0xEF41,
+        0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541,
+        0x2700, 0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340,
+        0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0, 0x6180, 0xA141,
+        0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740,
+        0xA501, 0x65C0, 0x6480, 0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40,
+        0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41,
+        0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940,
+        0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01, 0x7EC0, 0x7F80, 0xBF41,
+        0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541,
+        0x7700, 0xB7C1, 0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340,
+        0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181, 0x5140,
+        0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741,
+        0x5500, 0x95C1, 0x9481, 0x5440, 0x9C01, 0x5CC0, 0x5D80, 0x9D41,
+        0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40,
+        0x9901, 0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941,
+        0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1, 0x8F81, 0x4F40,
+        0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540,
+        0x8701, 0x47C0, 0x4680, 0x8641, 0x8201, 0x42C0, 0x4380, 0x8341,
+        0x4100, 0x81C1, 0x8081, 0x4040];
+		
+	//fix 精简
+	
+    /**
+     * Fast CRC-16 computation (uses table crc16Lookup).
+     *
+     * @param value
+     * @param crc
+     * @return
+     */
+    function crcUpdateLookup(value, crc) {
+        var tmp = crc ^ value;
+        crc = (crc >> 8) ^ crc16Lookup[tmp & 0xff];
+        return crc;
+    }
+
+    this.updateMusicCRC = function (crc, buffer, bufferPos, size) {
+        for (var i = 0; i < size; ++i)
+            crc[0] = crcUpdateLookup(buffer[bufferPos + i], crc[0]);
+    }
+
+	//fix 精简
+}
+
+
+
+BitStream.EQ = function (a, b) {
+    return (Math.abs(a) > Math.abs(b)) ? (Math.abs((a) - (b)) <= (Math
+        .abs(a) * 1e-6))
+        : (Math.abs((a) - (b)) <= (Math.abs(b) * 1e-6));
+};
+
+BitStream.NEQ = function (a, b) {
+    return !BitStream.EQ(a, b);
+};
+
+function BitStream() {
+    var self = this;
+    var CRC16_POLYNOMIAL = 0x8005;
+
+    /*
+     * we work with ints, so when doing bit manipulation, we limit ourselves to
+     * MAX_LENGTH-2 just to be on the safe side
+     */
+    var MAX_LENGTH = 32;
+
+    //GainAnalysis ga;
+    //MPGLib mpg;
+    //Version ver;
+    //VBRTag vbr;
+    var ga = null;
+    var mpg = null;
+    var ver = null;
+    var vbr = null;
+
+    //public final void setModules(GainAnalysis ga, MPGLib mpg, Version ver,
+    //	VBRTag vbr) {
+
+    this.setModules = function (_ga, _mpg, _ver, _vbr) {
+        ga = _ga;
+        mpg = _mpg;
+        ver = _ver;
+        vbr = _vbr;
+    };
+
+    /**
+     * Bit stream buffer.
+     */
+    //private byte[] buf;
+    var buf = null;
+    /**
+     * Bit counter of bit stream.
+     */
+    var totbit = 0;
+    /**
+     * Pointer to top byte in buffer.
+     */
+    var bufByteIdx = 0;
+    /**
+     * Pointer to top bit of top byte in buffer.
+     */
+    var bufBitIdx = 0;
+
+    /**
+     * compute bitsperframe and mean_bits for a layer III frame
+     */
+    this.getframebits = function (gfp) {
+        var gfc = gfp.internal_flags;
+        var bit_rate;
+
+        /* get bitrate in kbps [?] */
+        if (gfc.bitrate_index != 0)
+            bit_rate = Tables.bitrate_table[gfp.version][gfc.bitrate_index];
+        else
+            bit_rate = gfp.brate;
+
+        /* main encoding routine toggles padding on and off */
+        /* one Layer3 Slot consists of 8 bits */
+        var bytes = 0 | (gfp.version + 1) * 72000 * bit_rate / gfp.out_samplerate + gfc.padding;
+        return 8 * bytes;
+    };
+
+    function putheader_bits(gfc) {
+        System.arraycopy(gfc.header[gfc.w_ptr].buf, 0, buf, bufByteIdx, gfc.sideinfo_len);
+        bufByteIdx += gfc.sideinfo_len;
+        totbit += gfc.sideinfo_len * 8;
+        gfc.w_ptr = (gfc.w_ptr + 1) & (LameInternalFlags.MAX_HEADER_BUF - 1);
+    }
+
+    /**
+     * write j bits into the bit stream
+     */
+    function putbits2(gfc, val, j) {
+
+
+        while (j > 0) {
+            var k;
+            if (bufBitIdx == 0) {
+                bufBitIdx = 8;
+                bufByteIdx++;
+                if (gfc.header[gfc.w_ptr].write_timing == totbit) {
+                    putheader_bits(gfc);
+                }
+                buf[bufByteIdx] = 0;
+            }
+
+            k = Math.min(j, bufBitIdx);
+            j -= k;
+
+            bufBitIdx -= k;
+
+            /* 32 too large on 32 bit machines */
+
+            buf[bufByteIdx] |= ((val >> j) << bufBitIdx);
+            totbit += k;
+        }
+    }
+
+    //fix cc 精简
+
+    /**
+     * Some combinations of bitrate, Fs, and stereo make it impossible to stuff
+     * out a frame using just main_data, due to the limited number of bits to
+     * indicate main_data_length. In these situations, we put stuffing bits into
+     * the ancillary data...
+     */
+    function drain_into_ancillary(gfp, remainingBits) {
+        var gfc = gfp.internal_flags;
+        var i;
+
+        if (remainingBits >= 8) {
+            putbits2(gfc, 0x4c, 8);
+            remainingBits -= 8;
+        }
+        if (remainingBits >= 8) {
+            putbits2(gfc, 0x41, 8);
+            remainingBits -= 8;
+        }
+        if (remainingBits >= 8) {
+            putbits2(gfc, 0x4d, 8);
+            remainingBits -= 8;
+        }
+        if (remainingBits >= 8) {
+            putbits2(gfc, 0x45, 8);
+            remainingBits -= 8;
+        }
+
+        if (remainingBits >= 32) {
+            var version = ver.getLameShortVersion();
+            if (remainingBits >= 32)
+                for (i = 0; i < version.length && remainingBits >= 8; ++i) {
+                    remainingBits -= 8;
+                    putbits2(gfc, version.charCodeAt(i), 8); //fix 错误的使用charAt
+                }
+        }
+
+        for (; remainingBits >= 1; remainingBits -= 1) {
+            putbits2(gfc, gfc.ancillary_flag, 1);
+            gfc.ancillary_flag ^= (!gfp.disable_reservoir ? 1 : 0);
+        }
+
+
+    }
+
+    /**
+     * write N bits into the header
+     */
+    function writeheader(gfc, val, j) {
+        var ptr = gfc.header[gfc.h_ptr].ptr;
+
+        while (j > 0) {
+            var k = Math.min(j, 8 - (ptr & 7));
+            j -= k;
+            /* >> 32 too large for 32 bit machines */
+
+            gfc.header[gfc.h_ptr].buf[ptr >> 3] |= ((val >> j)) << (8 - (ptr & 7) - k);
+            ptr += k;
+        }
+        gfc.header[gfc.h_ptr].ptr = ptr;
+    }
+
+    //fix cc 精简
+
+    function encodeSideInfo2(gfp, bitsPerFrame) {
+        var gfc = gfp.internal_flags;
+        var l3_side;
+        var gr, ch;
+
+        l3_side = gfc.l3_side;
+        gfc.header[gfc.h_ptr].ptr = 0;
+        Arrays.fill(gfc.header[gfc.h_ptr].buf, 0, gfc.sideinfo_len, 0);
+        if (gfp.out_samplerate < 16000)
+            writeheader(gfc, 0xffe, 12);
+        else
+            writeheader(gfc, 0xfff, 12);
+        writeheader(gfc, (gfp.version), 1);
+        writeheader(gfc, 4 - 3, 2);
+        writeheader(gfc, (!gfp.error_protection ? 1 : 0), 1);
+        writeheader(gfc, (gfc.bitrate_index), 4);
+        writeheader(gfc, (gfc.samplerate_index), 2);
+        writeheader(gfc, (gfc.padding), 1);
+        writeheader(gfc, (gfp.extension), 1);
+        writeheader(gfc, (gfp.mode.ordinal()), 2);
+        writeheader(gfc, (gfc.mode_ext), 2);
+        writeheader(gfc, (gfp.copyright), 1);
+        writeheader(gfc, (gfp.original), 1);
+        writeheader(gfc, (gfp.emphasis), 2);
+        if (gfp.error_protection) {
+            writeheader(gfc, 0, 16);
+            /* dummy */
+        }
+
+        if (gfp.version == 1) {
+            /* MPEG1 */
+            writeheader(gfc, (l3_side.main_data_begin), 9);
+
+            if (gfc.channels_out == 2)
+                writeheader(gfc, l3_side.private_bits, 3);
+            else
+                writeheader(gfc, l3_side.private_bits, 5);
+
+            for (ch = 0; ch < gfc.channels_out; ch++) {
+                var band;
+                for (band = 0; band < 4; band++) {
+                    writeheader(gfc, l3_side.scfsi[ch][band], 1);
+                }
+            }
+
+            for (gr = 0; gr < 2; gr++) {
+                for (ch = 0; ch < gfc.channels_out; ch++) {
+                    var gi = l3_side.tt[gr][ch];
+                    writeheader(gfc, gi.part2_3_length + gi.part2_length, 12);
+                    writeheader(gfc, gi.big_values / 2, 9);
+                    writeheader(gfc, gi.global_gain, 8);
+                    writeheader(gfc, gi.scalefac_compress, 4);
+
+                    if (gi.block_type != Encoder.NORM_TYPE) {
+                        writeheader(gfc, 1, 1);
+                        /* window_switching_flag */
+                        writeheader(gfc, gi.block_type, 2);
+                        writeheader(gfc, gi.mixed_block_flag, 1);
+
+                        if (gi.table_select[0] == 14)
+                            gi.table_select[0] = 16;
+                        writeheader(gfc, gi.table_select[0], 5);
+                        if (gi.table_select[1] == 14)
+                            gi.table_select[1] = 16;
+                        writeheader(gfc, gi.table_select[1], 5);
+
+                        writeheader(gfc, gi.subblock_gain[0], 3);
+                        writeheader(gfc, gi.subblock_gain[1], 3);
+                        writeheader(gfc, gi.subblock_gain[2], 3);
+                    } else {
+                        writeheader(gfc, 0, 1);
+                        /* window_switching_flag */
+                        if (gi.table_select[0] == 14)
+                            gi.table_select[0] = 16;
+                        writeheader(gfc, gi.table_select[0], 5);
+                        if (gi.table_select[1] == 14)
+                            gi.table_select[1] = 16;
+                        writeheader(gfc, gi.table_select[1], 5);
+                        if (gi.table_select[2] == 14)
+                            gi.table_select[2] = 16;
+                        writeheader(gfc, gi.table_select[2], 5);
+
+                        writeheader(gfc, gi.region0_count, 4);
+                        writeheader(gfc, gi.region1_count, 3);
+                    }
+                    writeheader(gfc, gi.preflag, 1);
+                    writeheader(gfc, gi.scalefac_scale, 1);
+                    writeheader(gfc, gi.count1table_select, 1);
+                }
+            }
+        } else {
+            /* MPEG2 */
+            writeheader(gfc, (l3_side.main_data_begin), 8);
+            writeheader(gfc, l3_side.private_bits, gfc.channels_out);
+
+            gr = 0;
+            for (ch = 0; ch < gfc.channels_out; ch++) {
+                var gi = l3_side.tt[gr][ch];
+                writeheader(gfc, gi.part2_3_length + gi.part2_length, 12);
+                writeheader(gfc, gi.big_values / 2, 9);
+                writeheader(gfc, gi.global_gain, 8);
+                writeheader(gfc, gi.scalefac_compress, 9);
+
+                if (gi.block_type != Encoder.NORM_TYPE) {
+                    writeheader(gfc, 1, 1);
+                    /* window_switching_flag */
+                    writeheader(gfc, gi.block_type, 2);
+                    writeheader(gfc, gi.mixed_block_flag, 1);
+
+                    if (gi.table_select[0] == 14)
+                        gi.table_select[0] = 16;
+                    writeheader(gfc, gi.table_select[0], 5);
+                    if (gi.table_select[1] == 14)
+                        gi.table_select[1] = 16;
+                    writeheader(gfc, gi.table_select[1], 5);
+
+                    writeheader(gfc, gi.subblock_gain[0], 3);
+                    writeheader(gfc, gi.subblock_gain[1], 3);
+                    writeheader(gfc, gi.subblock_gain[2], 3);
+                } else {
+                    writeheader(gfc, 0, 1);
+                    /* window_switching_flag */
+                    if (gi.table_select[0] == 14)
+                        gi.table_select[0] = 16;
+                    writeheader(gfc, gi.table_select[0], 5);
+                    if (gi.table_select[1] == 14)
+                        gi.table_select[1] = 16;
+                    writeheader(gfc, gi.table_select[1], 5);
+                    if (gi.table_select[2] == 14)
+                        gi.table_select[2] = 16;
+                    writeheader(gfc, gi.table_select[2], 5);
+
+                    writeheader(gfc, gi.region0_count, 4);
+                    writeheader(gfc, gi.region1_count, 3);
+                }
+
+                writeheader(gfc, gi.scalefac_scale, 1);
+                writeheader(gfc, gi.count1table_select, 1);
+            }
+        }
+
+        if (gfp.error_protection) {
+            abort();//fix cc 精简
+        }
+
+        {
+            var old = gfc.h_ptr;
+
+            gfc.h_ptr = (old + 1) & (LameInternalFlags.MAX_HEADER_BUF - 1);
+            gfc.header[gfc.h_ptr].write_timing = gfc.header[old].write_timing
+                + bitsPerFrame;
+
+            if (gfc.h_ptr == gfc.w_ptr) {
+                /* yikes! we are out of header buffer space */
+                //fix cc 精简 print
+            }
+
+        }
+    }
+
+    function huffman_coder_count1(gfc, gi) {
+        /* Write count1 area */
+        var h = Tables.ht[gi.count1table_select + 32];
+        var i, bits = 0;
+
+        var ix = gi.big_values;
+        var xr = gi.big_values;
+
+        for (i = (gi.count1 - gi.big_values) / 4; i > 0; --i) {
+            var huffbits = 0;
+            var p = 0, v;
+
+            v = gi.l3_enc[ix + 0];
+            if (v != 0) {
+                p += 8;
+                if (gi.xr[xr + 0] < 0)
+                    huffbits++;
+            }
+
+            v = gi.l3_enc[ix + 1];
+            if (v != 0) {
+                p += 4;
+                huffbits *= 2;
+                if (gi.xr[xr + 1] < 0)
+                    huffbits++;
+            }
+
+            v = gi.l3_enc[ix + 2];
+            if (v != 0) {
+                p += 2;
+                huffbits *= 2;
+                if (gi.xr[xr + 2] < 0)
+                    huffbits++;
+            }
+
+            v = gi.l3_enc[ix + 3];
+            if (v != 0) {
+                p++;
+                huffbits *= 2;
+                if (gi.xr[xr + 3] < 0)
+                    huffbits++;
+            }
+
+            ix += 4;
+            xr += 4;
+            putbits2(gfc, huffbits + h.table[p], h.hlen[p]);
+            bits += h.hlen[p];
+        }
+        return bits;
+    }
+
+    /**
+     * Implements the pseudocode of page 98 of the IS
+     */
+    function Huffmancode(gfc, tableindex, start, end, gi) {
+        var h = Tables.ht[tableindex];
+        var bits = 0;
+
+        if (0 == tableindex)
+            return bits;
+
+        for (var i = start; i < end; i += 2) {
+            var cbits = 0;
+            var xbits = 0;
+            var linbits = h.xlen;
+            var xlen = h.xlen;
+            var ext = 0;
+            var x1 = gi.l3_enc[i];
+            var x2 = gi.l3_enc[i + 1];
+
+            if (x1 != 0) {
+                if (gi.xr[i] < 0)
+                    ext++;
+                cbits--;
+            }
+
+            if (tableindex > 15) {
+                /* use ESC-words */
+                if (x1 > 14) {
+                    var linbits_x1 = x1 - 15;
+                    ext |= linbits_x1 << 1;
+                    xbits = linbits;
+                    x1 = 15;
+                }
+
+                if (x2 > 14) {
+                    var linbits_x2 = x2 - 15;
+                    ext <<= linbits;
+                    ext |= linbits_x2;
+                    xbits += linbits;
+                    x2 = 15;
+                }
+                xlen = 16;
+            }
+
+            if (x2 != 0) {
+                ext <<= 1;
+                if (gi.xr[i + 1] < 0)
+                    ext++;
+                cbits--;
+            }
+
+
+            x1 = x1 * xlen + x2;
+            xbits -= cbits;
+            cbits += h.hlen[x1];
+
+
+            putbits2(gfc, h.table[x1], cbits);
+            putbits2(gfc, ext, xbits);
+            bits += cbits + xbits;
+        }
+        return bits;
+    }
+
+    /**
+     * Note the discussion of huffmancodebits() on pages 28 and 29 of the IS, as
+     * well as the definitions of the side information on pages 26 and 27.
+     */
+    function ShortHuffmancodebits(gfc, gi) {
+        var region1Start = 3 * gfc.scalefac_band.s[3];
+        if (region1Start > gi.big_values)
+            region1Start = gi.big_values;
+
+        /* short blocks do not have a region2 */
+        var bits = Huffmancode(gfc, gi.table_select[0], 0, region1Start, gi);
+        bits += Huffmancode(gfc, gi.table_select[1], region1Start,
+            gi.big_values, gi);
+        return bits;
+    }
+
+    function LongHuffmancodebits(gfc, gi) {
+        var bigvalues, bits;
+        var region1Start, region2Start;
+
+        bigvalues = gi.big_values;
+
+        var i = gi.region0_count + 1;
+        region1Start = gfc.scalefac_band.l[i];
+        i += gi.region1_count + 1;
+        region2Start = gfc.scalefac_band.l[i];
+
+        if (region1Start > bigvalues)
+            region1Start = bigvalues;
+
+        if (region2Start > bigvalues)
+            region2Start = bigvalues;
+
+        bits = Huffmancode(gfc, gi.table_select[0], 0, region1Start, gi);
+        bits += Huffmancode(gfc, gi.table_select[1], region1Start,
+            region2Start, gi);
+        bits += Huffmancode(gfc, gi.table_select[2], region2Start, bigvalues,
+            gi);
+        return bits;
+    }
+
+    function writeMainData(gfp) {
+        var gr, ch, sfb, data_bits, tot_bits = 0;
+        var gfc = gfp.internal_flags;
+        var l3_side = gfc.l3_side;
+
+        if (gfp.version == 1) {
+            /* MPEG 1 */
+            for (gr = 0; gr < 2; gr++) {
+                for (ch = 0; ch < gfc.channels_out; ch++) {
+                    var gi = l3_side.tt[gr][ch];
+                    var slen1 = Takehiro.slen1_tab[gi.scalefac_compress];
+                    var slen2 = Takehiro.slen2_tab[gi.scalefac_compress];
+                    data_bits = 0;
+                    for (sfb = 0; sfb < gi.sfbdivide; sfb++) {
+                        if (gi.scalefac[sfb] == -1)
+                            continue;
+                        /* scfsi is used */
+                        putbits2(gfc, gi.scalefac[sfb], slen1);
+                        data_bits += slen1;
+                    }
+                    for (; sfb < gi.sfbmax; sfb++) {
+                        if (gi.scalefac[sfb] == -1)
+                            continue;
+                        /* scfsi is used */
+                        putbits2(gfc, gi.scalefac[sfb], slen2);
+                        data_bits += slen2;
+                    }
+
+                    if (gi.block_type == Encoder.SHORT_TYPE) {
+                        data_bits += ShortHuffmancodebits(gfc, gi);
+                    } else {
+                        data_bits += LongHuffmancodebits(gfc, gi);
+                    }
+                    data_bits += huffman_coder_count1(gfc, gi);
+                    /* does bitcount in quantize.c agree with actual bit count? */
+                    tot_bits += data_bits;
+                }
+                /* for ch */
+            }
+            /* for gr */
+        } else {
+            /* MPEG 2 */
+            gr = 0;
+            for (ch = 0; ch < gfc.channels_out; ch++) {
+                var gi = l3_side.tt[gr][ch];
+                var i, sfb_partition, scale_bits = 0;
+                data_bits = 0;
+                sfb = 0;
+                sfb_partition = 0;
+
+                if (gi.block_type == Encoder.SHORT_TYPE) {
+                    for (; sfb_partition < 4; sfb_partition++) {
+                        var sfbs = gi.sfb_partition_table[sfb_partition] / 3;
+                        var slen = gi.slen[sfb_partition];
+                        for (i = 0; i < sfbs; i++, sfb++) {
+                            putbits2(gfc,
+                                Math.max(gi.scalefac[sfb * 3 + 0], 0), slen);
+                            putbits2(gfc,
+                                Math.max(gi.scalefac[sfb * 3 + 1], 0), slen);
+                            putbits2(gfc,
+                                Math.max(gi.scalefac[sfb * 3 + 2], 0), slen);
+                            scale_bits += 3 * slen;
+                        }
+                    }
+                    data_bits += ShortHuffmancodebits(gfc, gi);
+                } else {
+                    for (; sfb_partition < 4; sfb_partition++) {
+                        var sfbs = gi.sfb_partition_table[sfb_partition];
+                        var slen = gi.slen[sfb_partition];
+                        for (i = 0; i < sfbs; i++, sfb++) {
+                            putbits2(gfc, Math.max(gi.scalefac[sfb], 0), slen);
+                            scale_bits += slen;
+                        }
+                    }
+                    data_bits += LongHuffmancodebits(gfc, gi);
+                }
+                data_bits += huffman_coder_count1(gfc, gi);
+                /* does bitcount in quantize.c agree with actual bit count? */
+                tot_bits += scale_bits + data_bits;
+            }
+            /* for ch */
+        }
+        /* for gf */
+        return tot_bits;
+    }
+
+    /* main_data */
+
+    function TotalBytes() {
+        this.total = 0;
+    }
+
+    /*
+     * compute the number of bits required to flush all mp3 frames currently in
+     * the buffer. This should be the same as the reservoir size. Only call this
+     * routine between frames - i.e. only after all headers and data have been
+     * added to the buffer by format_bitstream().
+     *
+     * Also compute total_bits_output = size of mp3 buffer (including frame
+     * headers which may not have yet been send to the mp3 buffer) + number of
+     * bits needed to flush all mp3 frames.
+     *
+     * total_bytes_output is the size of the mp3 output buffer if
+     * lame_encode_flush_nogap() was called right now.
+     */
+    function compute_flushbits(gfp, total_bytes_output) {
+        var gfc = gfp.internal_flags;
+        var flushbits, remaining_headers;
+        var bitsPerFrame;
+        var last_ptr, first_ptr;
+        first_ptr = gfc.w_ptr;
+        /* first header to add to bitstream */
+        last_ptr = gfc.h_ptr - 1;
+        /* last header to add to bitstream */
+        if (last_ptr == -1)
+            last_ptr = LameInternalFlags.MAX_HEADER_BUF - 1;
+
+        /* add this many bits to bitstream so we can flush all headers */
+        flushbits = gfc.header[last_ptr].write_timing - totbit;
+        total_bytes_output.total = flushbits;
+
+        if (flushbits >= 0) {
+            abort();//fix cc 精简
+        }
+
+        /*
+         * finally, add some bits so that the last frame is complete these bits
+         * are not necessary to decode the last frame, but some decoders will
+         * ignore last frame if these bits are missing
+         */
+        bitsPerFrame = self.getframebits(gfp);
+        flushbits += bitsPerFrame;
+        total_bytes_output.total += bitsPerFrame;
+        /* round up: */
+        if ((total_bytes_output.total % 8) != 0)
+            total_bytes_output.total = 1 + (total_bytes_output.total / 8);
+        else
+            total_bytes_output.total = (total_bytes_output.total / 8);
+        total_bytes_output.total += bufByteIdx + 1;
+
+        if (flushbits < 0) {
+            //fix cc 精简 print
+        }
+        return flushbits;
+    }
+
+    this.flush_bitstream = function (gfp) {
+        var gfc = gfp.internal_flags;
+        var l3_side;
+        var flushbits;
+        var last_ptr = gfc.h_ptr - 1;
+        /* last header to add to bitstream */
+        if (last_ptr == -1)
+            last_ptr = LameInternalFlags.MAX_HEADER_BUF - 1;
+        l3_side = gfc.l3_side;
+
+        if ((flushbits = compute_flushbits(gfp, new TotalBytes())) < 0)
+            return;
+        drain_into_ancillary(gfp, flushbits);
+
+        /* check that the 100% of the last frame has been written to bitstream */
+
+        /*
+         * we have padded out all frames with ancillary data, which is the same
+         * as filling the bitreservoir with ancillary data, so :
+         */
+        gfc.ResvSize = 0;
+        l3_side.main_data_begin = 0;
+
+        /* save the ReplayGain value */
+        if (gfc.findReplayGain) {
+            abort();//fix cc 精简
+        }
+
+        /* find the gain and scale change required for no clipping */
+        if (gfc.findPeakSample) {
+            abort();//fix cc 精简
+        }
+    };
+
+    //fix cc 精简
+
+    /**
+     * This is called after a frame of audio has been quantized and coded. It
+     * will write the encoded audio to the bitstream. Note that from a layer3
+     * encoder's perspective the bit stream is primarily a series of main_data()
+     * blocks, with header and side information inserted at the proper locations
+     * to maintain framing. (See Figure A.7 in the IS).
+     */
+    this.format_bitstream = function (gfp) {
+        var gfc = gfp.internal_flags;
+        var l3_side;
+        l3_side = gfc.l3_side;
+
+        var bitsPerFrame = this.getframebits(gfp);
+        drain_into_ancillary(gfp, l3_side.resvDrain_pre);
+
+        encodeSideInfo2(gfp, bitsPerFrame);
+        var bits = 8 * gfc.sideinfo_len;
+        bits += writeMainData(gfp);
+        drain_into_ancillary(gfp, l3_side.resvDrain_post);
+        bits += l3_side.resvDrain_post;
+
+        l3_side.main_data_begin += (bitsPerFrame - bits) / 8;
+
+        /*
+         * compare number of bits needed to clear all buffered mp3 frames with
+         * what we think the resvsize is:
+         */
+        if (compute_flushbits(gfp, new TotalBytes()) != gfc.ResvSize) {
+            //fix cc 精简 print
+        }
+
+        /*
+         * compare main_data_begin for the next frame with what we think the
+         * resvsize is:
+         */
+        if ((l3_side.main_data_begin * 8) != gfc.ResvSize) {
+            //fix cc 精简 print
+            gfc.ResvSize = l3_side.main_data_begin * 8;
+        }
+        //;
+
+        if (totbit > 1000000000) { //不可精简
+            /*
+             * to avoid totbit overflow, (at 8h encoding at 128kbs) lets reset
+             * bit counter
+             */
+            var i;
+            for (i = 0; i < LameInternalFlags.MAX_HEADER_BUF; ++i)
+                gfc.header[i].write_timing -= totbit;
+            totbit = 0;
+        }
+
+        return 0;
+    };
+
+    /**
+     * <PRE>
+     * copy data out of the internal MP3 bit buffer into a user supplied
+     *       unsigned char buffer.
+     *
+     *       mp3data=0      indicates data in buffer is an id3tags and VBR tags
+     *       mp3data=1      data is real mp3 frame data.
+     * </PRE>
+     */
+    this.copy_buffer = function (gfc, buffer, bufferPos, size, mp3data) {
+        var minimum = bufByteIdx + 1;
+        if (minimum <= 0)
+            return 0;
+        if (size != 0 && minimum > size) {
+            /* buffer is too small */
+            return -1;
+        }
+        System.arraycopy(buf, 0, buffer, bufferPos, minimum);
+        bufByteIdx = -1;
+        bufBitIdx = 0;
+
+        if (mp3data != 0) {
+            var crc = new_int(1);
+            crc[0] = gfc.nMusicCRC;
+            vbr.updateMusicCRC(crc, buffer, bufferPos, minimum);
+            gfc.nMusicCRC = crc[0];
+
+            /**
+             * sum number of bytes belonging to the mp3 stream this info will be
+             * written into the Xing/LAME header for seeking
+             */
+            if (minimum > 0) {
+                gfc.VBR_seek_table.nBytesWritten += minimum;
+            }
+
+            if (gfc.decode_on_the_fly) { /* decode the frame */
+                abort();//fix cc 精简
+            }
+            /* if (gfc.decode_on_the_fly) */
+
+        }
+        /* if (mp3data) */
+        return minimum;
+    };
+
+    this.init_bit_stream_w = function (gfc) {
+        buf = new_byte(Lame.LAME_MAXMP3BUFFER);
+
+        gfc.h_ptr = gfc.w_ptr = 0;
+        gfc.header[gfc.h_ptr].write_timing = 0;
+        bufByteIdx = -1;
+        bufBitIdx = 0;
+        totbit = 0;
+    };
+
+    // From machine.h
+
+
+}
+
+function HuffCodeTab(len, max, tab, hl) {
+    this.xlen = len;
+    this.linmax = max;
+    this.table = tab;
+    this.hlen = hl;
+}
+
+var Tables = {};
+
+
+Tables.t1HB = [
+    1, 1,
+    1, 0
+];
+
+Tables.t2HB = [
+    1, 2, 1,
+    3, 1, 1,
+    3, 2, 0
+];
+
+Tables.t3HB = [
+    3, 2, 1,
+    1, 1, 1,
+    3, 2, 0
+];
+
+Tables.t5HB = [
+    1, 2, 6, 5,
+    3, 1, 4, 4,
+    7, 5, 7, 1,
+    6, 1, 1, 0
+];
+
+Tables.t6HB = [
+    7, 3, 5, 1,
+    6, 2, 3, 2,
+    5, 4, 4, 1,
+    3, 3, 2, 0
+];
+
+Tables.t7HB = [
+    1, 2, 10, 19, 16, 10,
+    3, 3, 7, 10, 5, 3,
+    11, 4, 13, 17, 8, 4,
+    12, 11, 18, 15, 11, 2,
+    7, 6, 9, 14, 3, 1,
+    6, 4, 5, 3, 2, 0
+];
+
+Tables.t8HB = [
+    3, 4, 6, 18, 12, 5,
+    5, 1, 2, 16, 9, 3,
+    7, 3, 5, 14, 7, 3,
+    19, 17, 15, 13, 10, 4,
+    13, 5, 8, 11, 5, 1,
+    12, 4, 4, 1, 1, 0
+];
+
+Tables.t9HB = [
+    7, 5, 9, 14, 15, 7,
+    6, 4, 5, 5, 6, 7,
+    7, 6, 8, 8, 8, 5,
+    15, 6, 9, 10, 5, 1,
+    11, 7, 9, 6, 4, 1,
+    14, 4, 6, 2, 6, 0
+];
+
+Tables.t10HB = [
+    1, 2, 10, 23, 35, 30, 12, 17,
+    3, 3, 8, 12, 18, 21, 12, 7,
+    11, 9, 15, 21, 32, 40, 19, 6,
+    14, 13, 22, 34, 46, 23, 18, 7,
+    20, 19, 33, 47, 27, 22, 9, 3,
+    31, 22, 41, 26, 21, 20, 5, 3,
+    14, 13, 10, 11, 16, 6, 5, 1,
+    9, 8, 7, 8, 4, 4, 2, 0
+];
+
+Tables.t11HB = [
+    3, 4, 10, 24, 34, 33, 21, 15,
+    5, 3, 4, 10, 32, 17, 11, 10,
+    11, 7, 13, 18, 30, 31, 20, 5,
+    25, 11, 19, 59, 27, 18, 12, 5,
+    35, 33, 31, 58, 30, 16, 7, 5,
+    28, 26, 32, 19, 17, 15, 8, 14,
+    14, 12, 9, 13, 14, 9, 4, 1,
+    11, 4, 6, 6, 6, 3, 2, 0
+];
+
+Tables.t12HB = [
+    9, 6, 16, 33, 41, 39, 38, 26,
+    7, 5, 6, 9, 23, 16, 26, 11,
+    17, 7, 11, 14, 21, 30, 10, 7,
+    17, 10, 15, 12, 18, 28, 14, 5,
+    32, 13, 22, 19, 18, 16, 9, 5,
+    40, 17, 31, 29, 17, 13, 4, 2,
+    27, 12, 11, 15, 10, 7, 4, 1,
+    27, 12, 8, 12, 6, 3, 1, 0
+];
+
+Tables.t13HB = [
+    1, 5, 14, 21, 34, 51, 46, 71, 42, 52, 68, 52, 67, 44, 43, 19,
+    3, 4, 12, 19, 31, 26, 44, 33, 31, 24, 32, 24, 31, 35, 22, 14,
+    15, 13, 23, 36, 59, 49, 77, 65, 29, 40, 30, 40, 27, 33, 42, 16,
+    22, 20, 37, 61, 56, 79, 73, 64, 43, 76, 56, 37, 26, 31, 25, 14,
+    35, 16, 60, 57, 97, 75, 114, 91, 54, 73, 55, 41, 48, 53, 23, 24,
+    58, 27, 50, 96, 76, 70, 93, 84, 77, 58, 79, 29, 74, 49, 41, 17,
+    47, 45, 78, 74, 115, 94, 90, 79, 69, 83, 71, 50, 59, 38, 36, 15,
+    72, 34, 56, 95, 92, 85, 91, 90, 86, 73, 77, 65, 51, 44, 43, 42,
+    43, 20, 30, 44, 55, 78, 72, 87, 78, 61, 46, 54, 37, 30, 20, 16,
+    53, 25, 41, 37, 44, 59, 54, 81, 66, 76, 57, 54, 37, 18, 39, 11,
+    35, 33, 31, 57, 42, 82, 72, 80, 47, 58, 55, 21, 22, 26, 38, 22,
+    53, 25, 23, 38, 70, 60, 51, 36, 55, 26, 34, 23, 27, 14, 9, 7,
+    34, 32, 28, 39, 49, 75, 30, 52, 48, 40, 52, 28, 18, 17, 9, 5,
+    45, 21, 34, 64, 56, 50, 49, 45, 31, 19, 12, 15, 10, 7, 6, 3,
+    48, 23, 20, 39, 36, 35, 53, 21, 16, 23, 13, 10, 6, 1, 4, 2,
+    16, 15, 17, 27, 25, 20, 29, 11, 17, 12, 16, 8, 1, 1, 0, 1
+];
+
+Tables.t15HB = [
+    7, 12, 18, 53, 47, 76, 124, 108, 89, 123, 108, 119, 107, 81, 122, 63,
+    13, 5, 16, 27, 46, 36, 61, 51, 42, 70, 52, 83, 65, 41, 59, 36,
+    19, 17, 15, 24, 41, 34, 59, 48, 40, 64, 50, 78, 62, 80, 56, 33,
+    29, 28, 25, 43, 39, 63, 55, 93, 76, 59, 93, 72, 54, 75, 50, 29,
+    52, 22, 42, 40, 67, 57, 95, 79, 72, 57, 89, 69, 49, 66, 46, 27,
+    77, 37, 35, 66, 58, 52, 91, 74, 62, 48, 79, 63, 90, 62, 40, 38,
+    125, 32, 60, 56, 50, 92, 78, 65, 55, 87, 71, 51, 73, 51, 70, 30,
+    109, 53, 49, 94, 88, 75, 66, 122, 91, 73, 56, 42, 64, 44, 21, 25,
+    90, 43, 41, 77, 73, 63, 56, 92, 77, 66, 47, 67, 48, 53, 36, 20,
+    71, 34, 67, 60, 58, 49, 88, 76, 67, 106, 71, 54, 38, 39, 23, 15,
+    109, 53, 51, 47, 90, 82, 58, 57, 48, 72, 57, 41, 23, 27, 62, 9,
+    86, 42, 40, 37, 70, 64, 52, 43, 70, 55, 42, 25, 29, 18, 11, 11,
+    118, 68, 30, 55, 50, 46, 74, 65, 49, 39, 24, 16, 22, 13, 14, 7,
+    91, 44, 39, 38, 34, 63, 52, 45, 31, 52, 28, 19, 14, 8, 9, 3,
+    123, 60, 58, 53, 47, 43, 32, 22, 37, 24, 17, 12, 15, 10, 2, 1,
+    71, 37, 34, 30, 28, 20, 17, 26, 21, 16, 10, 6, 8, 6, 2, 0
+];
+
+Tables.t16HB = [
+    1, 5, 14, 44, 74, 63, 110, 93, 172, 149, 138, 242, 225, 195, 376, 17,
+    3, 4, 12, 20, 35, 62, 53, 47, 83, 75, 68, 119, 201, 107, 207, 9,
+    15, 13, 23, 38, 67, 58, 103, 90, 161, 72, 127, 117, 110, 209, 206, 16,
+    45, 21, 39, 69, 64, 114, 99, 87, 158, 140, 252, 212, 199, 387, 365, 26,
+    75, 36, 68, 65, 115, 101, 179, 164, 155, 264, 246, 226, 395, 382, 362, 9,
+    66, 30, 59, 56, 102, 185, 173, 265, 142, 253, 232, 400, 388, 378, 445, 16,
+    111, 54, 52, 100, 184, 178, 160, 133, 257, 244, 228, 217, 385, 366, 715, 10,
+    98, 48, 91, 88, 165, 157, 148, 261, 248, 407, 397, 372, 380, 889, 884, 8,
+    85, 84, 81, 159, 156, 143, 260, 249, 427, 401, 392, 383, 727, 713, 708, 7,
+    154, 76, 73, 141, 131, 256, 245, 426, 406, 394, 384, 735, 359, 710, 352, 11,
+    139, 129, 67, 125, 247, 233, 229, 219, 393, 743, 737, 720, 885, 882, 439, 4,
+    243, 120, 118, 115, 227, 223, 396, 746, 742, 736, 721, 712, 706, 223, 436, 6,
+    202, 224, 222, 218, 216, 389, 386, 381, 364, 888, 443, 707, 440, 437, 1728, 4,
+    747, 211, 210, 208, 370, 379, 734, 723, 714, 1735, 883, 877, 876, 3459, 865, 2,
+    377, 369, 102, 187, 726, 722, 358, 711, 709, 866, 1734, 871, 3458, 870, 434, 0,
+    12, 10, 7, 11, 10, 17, 11, 9, 13, 12, 10, 7, 5, 3, 1, 3
+];
+
+Tables.t24HB = [
+    15, 13, 46, 80, 146, 262, 248, 434, 426, 669, 653, 649, 621, 517, 1032, 88,
+    14, 12, 21, 38, 71, 130, 122, 216, 209, 198, 327, 345, 319, 297, 279, 42,
+    47, 22, 41, 74, 68, 128, 120, 221, 207, 194, 182, 340, 315, 295, 541, 18,
+    81, 39, 75, 70, 134, 125, 116, 220, 204, 190, 178, 325, 311, 293, 271, 16,
+    147, 72, 69, 135, 127, 118, 112, 210, 200, 188, 352, 323, 306, 285, 540, 14,
+    263, 66, 129, 126, 119, 114, 214, 202, 192, 180, 341, 317, 301, 281, 262, 12,
+    249, 123, 121, 117, 113, 215, 206, 195, 185, 347, 330, 308, 291, 272, 520, 10,
+    435, 115, 111, 109, 211, 203, 196, 187, 353, 332, 313, 298, 283, 531, 381, 17,
+    427, 212, 208, 205, 201, 193, 186, 177, 169, 320, 303, 286, 268, 514, 377, 16,
+    335, 199, 197, 191, 189, 181, 174, 333, 321, 305, 289, 275, 521, 379, 371, 11,
+    668, 184, 183, 179, 175, 344, 331, 314, 304, 290, 277, 530, 383, 373, 366, 10,
+    652, 346, 171, 168, 164, 318, 309, 299, 287, 276, 263, 513, 375, 368, 362, 6,
+    648, 322, 316, 312, 307, 302, 292, 284, 269, 261, 512, 376, 370, 364, 359, 4,
+    620, 300, 296, 294, 288, 282, 273, 266, 515, 380, 374, 369, 365, 361, 357, 2,
+    1033, 280, 278, 274, 267, 264, 259, 382, 378, 372, 367, 363, 360, 358, 356, 0,
+    43, 20, 19, 17, 15, 13, 11, 9, 7, 6, 4, 7, 5, 3, 1, 3
+];
+
+Tables.t32HB = [
+    1 << 0, 5 << 1, 4 << 1, 5 << 2, 6 << 1, 5 << 2, 4 << 2, 4 << 3,
+    7 << 1, 3 << 2, 6 << 2, 0 << 3, 7 << 2, 2 << 3, 3 << 3, 1 << 4
+];
+
+Tables.t33HB = [
+    15 << 0, 14 << 1, 13 << 1, 12 << 2, 11 << 1, 10 << 2, 9 << 2, 8 << 3,
+    7 << 1, 6 << 2, 5 << 2, 4 << 3, 3 << 2, 2 << 3, 1 << 3, 0 << 4
+];
+
+Tables.t1l = [
+    1, 4,
+    3, 5
+];
+
+Tables.t2l = [
+    1, 4, 7,
+    4, 5, 7,
+    6, 7, 8
+];
+
+Tables.t3l = [
+    2, 3, 7,
+    4, 4, 7,
+    6, 7, 8
+];
+
+Tables.t5l = [
+    1, 4, 7, 8,
+    4, 5, 8, 9,
+    7, 8, 9, 10,
+    8, 8, 9, 10
+];
+
+Tables.t6l = [
+    3, 4, 6, 8,
+    4, 4, 6, 7,
+    5, 6, 7, 8,
+    7, 7, 8, 9
+];
+
+Tables.t7l = [
+    1, 4, 7, 9, 9, 10,
+    4, 6, 8, 9, 9, 10,
+    7, 7, 9, 10, 10, 11,
+    8, 9, 10, 11, 11, 11,
+    8, 9, 10, 11, 11, 12,
+    9, 10, 11, 12, 12, 12
+];
+
+Tables.t8l = [
+    2, 4, 7, 9, 9, 10,
+    4, 4, 6, 10, 10, 10,
+    7, 6, 8, 10, 10, 11,
+    9, 10, 10, 11, 11, 12,
+    9, 9, 10, 11, 12, 12,
+    10, 10, 11, 11, 13, 13
+];
+
+Tables.t9l = [
+    3, 4, 6, 7, 9, 10,
+    4, 5, 6, 7, 8, 10,
+    5, 6, 7, 8, 9, 10,
+    7, 7, 8, 9, 9, 10,
+    8, 8, 9, 9, 10, 11,
+    9, 9, 10, 10, 11, 11
+];
+
+Tables.t10l = [
+    1, 4, 7, 9, 10, 10, 10, 11,
+    4, 6, 8, 9, 10, 11, 10, 10,
+    7, 8, 9, 10, 11, 12, 11, 11,
+    8, 9, 10, 11, 12, 12, 11, 12,
+    9, 10, 11, 12, 12, 12, 12, 12,
+    10, 11, 12, 12, 13, 13, 12, 13,
+    9, 10, 11, 12, 12, 12, 13, 13,
+    10, 10, 11, 12, 12, 13, 13, 13
+];
+
+Tables.t11l = [
+    2, 4, 6, 8, 9, 10, 9, 10,
+    4, 5, 6, 8, 10, 10, 9, 10,
+    6, 7, 8, 9, 10, 11, 10, 10,
+    8, 8, 9, 11, 10, 12, 10, 11,
+    9, 10, 10, 11, 11, 12, 11, 12,
+    9, 10, 11, 12, 12, 13, 12, 13,
+    9, 9, 9, 10, 11, 12, 12, 12,
+    9, 9, 10, 11, 12, 12, 12, 12
+];
+
+Tables.t12l = [
+    4, 4, 6, 8, 9, 10, 10, 10,
+    4, 5, 6, 7, 9, 9, 10, 10,
+    6, 6, 7, 8, 9, 10, 9, 10,
+    7, 7, 8, 8, 9, 10, 10, 10,
+    8, 8, 9, 9, 10, 10, 10, 11,
+    9, 9, 10, 10, 10, 11, 10, 11,
+    9, 9, 9, 10, 10, 11, 11, 12,
+    10, 10, 10, 11, 11, 11, 11, 12
+];
+
+Tables.t13l = [
+    1, 5, 7, 8, 9, 10, 10, 11, 10, 11, 12, 12, 13, 13, 14, 14,
+    4, 6, 8, 9, 10, 10, 11, 11, 11, 11, 12, 12, 13, 14, 14, 14,
+    7, 8, 9, 10, 11, 11, 12, 12, 11, 12, 12, 13, 13, 14, 15, 15,
+    8, 9, 10, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 15, 15,
+    9, 9, 11, 11, 12, 12, 13, 13, 12, 13, 13, 14, 14, 15, 15, 16,
+    10, 10, 11, 12, 12, 12, 13, 13, 13, 13, 14, 13, 15, 15, 16, 16,
+    10, 11, 12, 12, 13, 13, 13, 13, 13, 14, 14, 14, 15, 15, 16, 16,
+    11, 11, 12, 13, 13, 13, 14, 14, 14, 14, 15, 15, 15, 16, 18, 18,
+    10, 10, 11, 12, 12, 13, 13, 14, 14, 14, 14, 15, 15, 16, 17, 17,
+    11, 11, 12, 12, 13, 13, 13, 15, 14, 15, 15, 16, 16, 16, 18, 17,
+    11, 12, 12, 13, 13, 14, 14, 15, 14, 15, 16, 15, 16, 17, 18, 19,
+    12, 12, 12, 13, 14, 14, 14, 14, 15, 15, 15, 16, 17, 17, 17, 18,
+    12, 13, 13, 14, 14, 15, 14, 15, 16, 16, 17, 17, 17, 18, 18, 18,
+    13, 13, 14, 15, 15, 15, 16, 16, 16, 16, 16, 17, 18, 17, 18, 18,
+    14, 14, 14, 15, 15, 15, 17, 16, 16, 19, 17, 17, 17, 19, 18, 18,
+    13, 14, 15, 16, 16, 16, 17, 16, 17, 17, 18, 18, 21, 20, 21, 18
+];
+
+Tables.t15l = [
+    3, 5, 6, 8, 8, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 14,
+    5, 5, 7, 8, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13,
+    6, 7, 7, 8, 9, 9, 10, 10, 10, 11, 11, 12, 12, 13, 13, 13,
+    7, 8, 8, 9, 9, 10, 10, 11, 11, 11, 12, 12, 12, 13, 13, 13,
+    8, 8, 9, 9, 10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 13, 13,
+    9, 9, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12, 13, 13, 13, 14,
+    10, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 13, 14, 14,
+    10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 14,
+    10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 14, 14, 14,
+    10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14,
+    11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 13, 14, 15, 14,
+    11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 15,
+    12, 12, 11, 12, 12, 12, 13, 13, 13, 13, 13, 13, 14, 14, 15, 15,
+    12, 12, 12, 12, 12, 13, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15,
+    13, 13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 14, 15,
+    13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 15, 15, 15, 15
+];
+
+Tables.t16_5l = [
+    1, 5, 7, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, 14, 11,
+    4, 6, 8, 9, 10, 11, 11, 11, 12, 12, 12, 13, 14, 13, 14, 11,
+    7, 8, 9, 10, 11, 11, 12, 12, 13, 12, 13, 13, 13, 14, 14, 12,
+    9, 9, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 14, 15, 15, 13,
+    10, 10, 11, 11, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 12,
+    10, 10, 11, 11, 12, 13, 13, 14, 13, 14, 14, 15, 15, 15, 16, 13,
+    11, 11, 11, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 16, 13,
+    11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 15, 15, 17, 17, 13,
+    11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 13,
+    12, 12, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 15, 16, 15, 14,
+    12, 13, 12, 13, 14, 14, 14, 14, 15, 16, 16, 16, 17, 17, 16, 13,
+    13, 13, 13, 13, 14, 14, 15, 16, 16, 16, 16, 16, 16, 15, 16, 14,
+    13, 14, 14, 14, 14, 15, 15, 15, 15, 17, 16, 16, 16, 16, 18, 14,
+    15, 14, 14, 14, 15, 15, 16, 16, 16, 18, 17, 17, 17, 19, 17, 14,
+    14, 15, 13, 14, 16, 16, 15, 16, 16, 17, 18, 17, 19, 17, 16, 14,
+    11, 11, 11, 12, 12, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 12
+];
+
+Tables.t16l = [
+    1, 5, 7, 9, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, 14, 10,
+    4, 6, 8, 9, 10, 11, 11, 11, 12, 12, 12, 13, 14, 13, 14, 10,
+    7, 8, 9, 10, 11, 11, 12, 12, 13, 12, 13, 13, 13, 14, 14, 11,
+    9, 9, 10, 11, 11, 12, 12, 12, 13, 13, 14, 14, 14, 15, 15, 12,
+    10, 10, 11, 11, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, 11,
+    10, 10, 11, 11, 12, 13, 13, 14, 13, 14, 14, 15, 15, 15, 16, 12,
+    11, 11, 11, 12, 13, 13, 13, 13, 14, 14, 14, 14, 15, 15, 16, 12,
+    11, 11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 15, 15, 17, 17, 12,
+    11, 12, 12, 13, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 12,
+    12, 12, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 15, 16, 15, 13,
+    12, 13, 12, 13, 14, 14, 14, 14, 15, 16, 16, 16, 17, 17, 16, 12,
+    13, 13, 13, 13, 14, 14, 15, 16, 16, 16, 16, 16, 16, 15, 16, 13,
+    13, 14, 14, 14, 14, 15, 15, 15, 15, 17, 16, 16, 16, 16, 18, 13,
+    15, 14, 14, 14, 15, 15, 16, 16, 16, 18, 17, 17, 17, 19, 17, 13,
+    14, 15, 13, 14, 16, 16, 15, 16, 16, 17, 18, 17, 19, 17, 16, 13,
+    10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 10
+];
+
+Tables.t24l = [
+    4, 5, 7, 8, 9, 10, 10, 11, 11, 12, 12, 12, 12, 12, 13, 10,
+    5, 6, 7, 8, 9, 10, 10, 11, 11, 11, 12, 12, 12, 12, 12, 10,
+    7, 7, 8, 9, 9, 10, 10, 11, 11, 11, 11, 12, 12, 12, 13, 9,
+    8, 8, 9, 9, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 9,
+    9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 12, 12, 12, 12, 13, 9,
+    10, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, 9,
+    10, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 9,
+    11, 10, 10, 10, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 10,
+    11, 11, 11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 13, 13, 10,
+    11, 11, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12, 13, 13, 13, 10,
+    12, 11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 10,
+    12, 12, 11, 11, 11, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 10,
+    12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 10,
+    12, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 10,
+    13, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13, 13, 13, 13, 10,
+    9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 10, 10, 10, 10, 6
+];
+
+Tables.t32l = [
+    1 + 0, 4 + 1, 4 + 1, 5 + 2, 4 + 1, 6 + 2, 5 + 2, 6 + 3,
+    4 + 1, 5 + 2, 5 + 2, 6 + 3, 5 + 2, 6 + 3, 6 + 3, 6 + 4
+];
+
+Tables.t33l = [
+    4 + 0, 4 + 1, 4 + 1, 4 + 2, 4 + 1, 4 + 2, 4 + 2, 4 + 3,
+    4 + 1, 4 + 2, 4 + 2, 4 + 3, 4 + 2, 4 + 3, 4 + 3, 4 + 4
+];
+
+Tables.ht = [
+    /* xlen, linmax, table, hlen */
+    new HuffCodeTab(0, 0, null, null),
+    new HuffCodeTab(2, 0, Tables.t1HB, Tables.t1l),
+    new HuffCodeTab(3, 0, Tables.t2HB, Tables.t2l),
+    new HuffCodeTab(3, 0, Tables.t3HB, Tables.t3l),
+    new HuffCodeTab(0, 0, null, null), /* Apparently not used */
+    new HuffCodeTab(4, 0, Tables.t5HB, Tables.t5l),
+    new HuffCodeTab(4, 0, Tables.t6HB, Tables.t6l),
+    new HuffCodeTab(6, 0, Tables.t7HB, Tables.t7l),
+    new HuffCodeTab(6, 0, Tables.t8HB, Tables.t8l),
+    new HuffCodeTab(6, 0, Tables.t9HB, Tables.t9l),
+    new HuffCodeTab(8, 0, Tables.t10HB, Tables.t10l),
+    new HuffCodeTab(8, 0, Tables.t11HB, Tables.t11l),
+    new HuffCodeTab(8, 0, Tables.t12HB, Tables.t12l),
+    new HuffCodeTab(16, 0, Tables.t13HB, Tables.t13l),
+    new HuffCodeTab(0, 0, null, Tables.t16_5l), /* Apparently not used */
+    new HuffCodeTab(16, 0, Tables.t15HB, Tables.t15l),
+
+    new HuffCodeTab(1, 1, Tables.t16HB, Tables.t16l),
+    new HuffCodeTab(2, 3, Tables.t16HB, Tables.t16l),
+    new HuffCodeTab(3, 7, Tables.t16HB, Tables.t16l),
+    new HuffCodeTab(4, 15, Tables.t16HB, Tables.t16l),
+    new HuffCodeTab(6, 63, Tables.t16HB, Tables.t16l),
+    new HuffCodeTab(8, 255, Tables.t16HB, Tables.t16l),
+    new HuffCodeTab(10, 1023, Tables.t16HB, Tables.t16l),
+    new HuffCodeTab(13, 8191, Tables.t16HB, Tables.t16l),
+
+    new HuffCodeTab(4, 15, Tables.t24HB, Tables.t24l),
+    new HuffCodeTab(5, 31, Tables.t24HB, Tables.t24l),
+    new HuffCodeTab(6, 63, Tables.t24HB, Tables.t24l),
+    new HuffCodeTab(7, 127, Tables.t24HB, Tables.t24l),
+    new HuffCodeTab(8, 255, Tables.t24HB, Tables.t24l),
+    new HuffCodeTab(9, 511, Tables.t24HB, Tables.t24l),
+    new HuffCodeTab(11, 2047, Tables.t24HB, Tables.t24l),
+    new HuffCodeTab(13, 8191, Tables.t24HB, Tables.t24l),
+
+    new HuffCodeTab(0, 0, Tables.t32HB, Tables.t32l),
+    new HuffCodeTab(0, 0, Tables.t33HB, Tables.t33l),
+];
+
+/**
+ * <CODE>
+ *  for (i = 0; i < 16*16; i++) [
+ *      largetbl[i] = ((ht[16].hlen[i]) << 16) + ht[24].hlen[i];
+ *  ]
+ * </CODE>
+ *
+ */
+Tables.largetbl = [
+    0x010004, 0x050005, 0x070007, 0x090008, 0x0a0009, 0x0a000a, 0x0b000a, 0x0b000b,
+    0x0c000b, 0x0c000c, 0x0c000c, 0x0d000c, 0x0d000c, 0x0d000c, 0x0e000d, 0x0a000a,
+    0x040005, 0x060006, 0x080007, 0x090008, 0x0a0009, 0x0b000a, 0x0b000a, 0x0b000b,
+    0x0c000b, 0x0c000b, 0x0c000c, 0x0d000c, 0x0e000c, 0x0d000c, 0x0e000c, 0x0a000a,
+    0x070007, 0x080007, 0x090008, 0x0a0009, 0x0b0009, 0x0b000a, 0x0c000a, 0x0c000b,
+    0x0d000b, 0x0c000b, 0x0d000b, 0x0d000c, 0x0d000c, 0x0e000c, 0x0e000d, 0x0b0009,
+    0x090008, 0x090008, 0x0a0009, 0x0b0009, 0x0b000a, 0x0c000a, 0x0c000a, 0x0c000b,
+    0x0d000b, 0x0d000b, 0x0e000b, 0x0e000c, 0x0e000c, 0x0f000c, 0x0f000c, 0x0c0009,
+    0x0a0009, 0x0a0009, 0x0b0009, 0x0b000a, 0x0c000a, 0x0c000a, 0x0d000a, 0x0d000b,
+    0x0d000b, 0x0e000b, 0x0e000c, 0x0e000c, 0x0f000c, 0x0f000c, 0x0f000d, 0x0b0009,
+    0x0a000a, 0x0a0009, 0x0b000a, 0x0b000a, 0x0c000a, 0x0d000a, 0x0d000b, 0x0e000b,
+    0x0d000b, 0x0e000b, 0x0e000c, 0x0f000c, 0x0f000c, 0x0f000c, 0x10000c, 0x0c0009,
+    0x0b000a, 0x0b000a, 0x0b000a, 0x0c000a, 0x0d000a, 0x0d000b, 0x0d000b, 0x0d000b,
+    0x0e000b, 0x0e000c, 0x0e000c, 0x0e000c, 0x0f000c, 0x0f000c, 0x10000d, 0x0c0009,
+    0x0b000b, 0x0b000a, 0x0c000a, 0x0c000a, 0x0d000b, 0x0d000b, 0x0d000b, 0x0e000b,
+    0x0e000c, 0x0f000c, 0x0f000c, 0x0f000c, 0x0f000c, 0x11000d, 0x11000d, 0x0c000a,
+    0x0b000b, 0x0c000b, 0x0c000b, 0x0d000b, 0x0d000b, 0x0d000b, 0x0e000b, 0x0e000b,
+    0x0f000b, 0x0f000c, 0x0f000c, 0x0f000c, 0x10000c, 0x10000d, 0x10000d, 0x0c000a,
+    0x0c000b, 0x0c000b, 0x0c000b, 0x0d000b, 0x0d000b, 0x0e000b, 0x0e000b, 0x0f000c,
+    0x0f000c, 0x0f000c, 0x0f000c, 0x10000c, 0x0f000d, 0x10000d, 0x0f000d, 0x0d000a,
+    0x0c000c, 0x0d000b, 0x0c000b, 0x0d000b, 0x0e000b, 0x0e000c, 0x0e000c, 0x0e000c,
+    0x0f000c, 0x10000c, 0x10000c, 0x10000d, 0x11000d, 0x11000d, 0x10000d, 0x0c000a,
+    0x0d000c, 0x0d000c, 0x0d000b, 0x0d000b, 0x0e000b, 0x0e000c, 0x0f000c, 0x10000c,
+    0x10000c, 0x10000c, 0x10000c, 0x10000d, 0x10000d, 0x0f000d, 0x10000d, 0x0d000a,
+    0x0d000c, 0x0e000c, 0x0e000c, 0x0e000c, 0x0e000c, 0x0f000c, 0x0f000c, 0x0f000c,
+    0x0f000c, 0x11000c, 0x10000d, 0x10000d, 0x10000d, 0x10000d, 0x12000d, 0x0d000a,
+    0x0f000c, 0x0e000c, 0x0e000c, 0x0e000c, 0x0f000c, 0x0f000c, 0x10000c, 0x10000c,
+    0x10000d, 0x12000d, 0x11000d, 0x11000d, 0x11000d, 0x13000d, 0x11000d, 0x0d000a,
+    0x0e000d, 0x0f000c, 0x0d000c, 0x0e000c, 0x10000c, 0x10000c, 0x0f000c, 0x10000d,
+    0x10000d, 0x11000d, 0x12000d, 0x11000d, 0x13000d, 0x11000d, 0x10000d, 0x0d000a,
+    0x0a0009, 0x0a0009, 0x0a0009, 0x0b0009, 0x0b0009, 0x0c0009, 0x0c0009, 0x0c0009,
+    0x0d0009, 0x0d0009, 0x0d0009, 0x0d000a, 0x0d000a, 0x0d000a, 0x0d000a, 0x0a0006
+];
+/**
+ * <CODE>
+ *  for (i = 0; i < 3*3; i++) [
+ *      table23[i] = ((ht[2].hlen[i]) << 16) + ht[3].hlen[i];
+ *  ]
+ * </CODE>
+ *
+ */
+Tables.table23 = [
+    0x010002, 0x040003, 0x070007,
+    0x040004, 0x050004, 0x070007,
+    0x060006, 0x070007, 0x080008
+];
+
+/**
+ * <CODE>
+ *  for (i = 0; i < 4*4; i++) [
+ *       table56[i] = ((ht[5].hlen[i]) << 16) + ht[6].hlen[i];
+ *   ]
+ * </CODE>
+ *
+ */
+Tables.table56 = [
+    0x010003, 0x040004, 0x070006, 0x080008, 0x040004, 0x050004, 0x080006, 0x090007,
+    0x070005, 0x080006, 0x090007, 0x0a0008, 0x080007, 0x080007, 0x090008, 0x0a0009
+];
+
+Tables.bitrate_table = [
+    [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, -1], /* MPEG 2 */
+    [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1], /* MPEG 1 */
+    [0, 8, 16, 24, 32, 40, 48, 56, 64, -1, -1, -1, -1, -1, -1, -1], /* MPEG 2.5 */
+];
+
+/**
+ * MPEG 2, MPEG 1, MPEG 2.5.
+ */
+Tables.samplerate_table = [
+    [22050, 24000, 16000, -1],
+    [44100, 48000, 32000, -1],
+    [11025, 12000, 8000, -1],
+];
+
+/**
+ * This is the scfsi_band table from 2.4.2.7 of the IS.
+ */
+Tables.scfsi_band = [0, 6, 11, 16, 21];
+
+function MeanBits(meanBits) {
+    this.bits = meanBits;
+}
+
+//package mp3;
+
+function CalcNoiseResult() {
+    /**
+     * sum of quantization noise > masking
+     */
+    this.over_noise = 0.;
+    /**
+     * sum of all quantization noise
+     */
+    this.tot_noise = 0.;
+    /**
+     * max quantization noise
+     */
+    this.max_noise = 0.;
+    /**
+     * number of quantization noise > masking
+     */
+    this.over_count = 0;
+    /**
+     * SSD-like cost of distorted bands
+     */
+    this.over_SSD = 0;
+    this.bits = 0;
+}
+
+function VBRQuantize() {
+    var qupvt;
+    var tak;
+
+    this.setModules = function (_qupvt, _tk) {
+        qupvt = _qupvt;
+        tak = _tk;
+    }
+    //TODO
+
+}
+
+
+
+/**
+ * ATH related stuff, if something new ATH related has to be added, please plug
+ * it here into the ATH.
+ */
+function ATH() {
+    /**
+     * Method for the auto adjustment.
+     */
+    this.useAdjust = 0;
+    /**
+     * factor for tuning the (sample power) point below which adaptive threshold
+     * of hearing adjustment occurs
+     */
+    this.aaSensitivityP = 0.;
+    /**
+     * Lowering based on peak volume, 1 = no lowering.
+     */
+    this.adjust = 0.;
+    /**
+     * Limit for dynamic ATH adjust.
+     */
+    this.adjustLimit = 0.;
+    /**
+     * Determined to lower x dB each second.
+     */
+    this.decay = 0.;
+    /**
+     * Lowest ATH value.
+     */
+    this.floor = 0.;
+    /**
+     * ATH for sfbs in long blocks.
+     */
+    this.l = new_float(Encoder.SBMAX_l);
+    /**
+     * ATH for sfbs in short blocks.
+     */
+    this.s = new_float(Encoder.SBMAX_s);
+    /**
+     * ATH for partitioned sfb21 in long blocks.
+     */
+    this.psfb21 = new_float(Encoder.PSFB21);
+    /**
+     * ATH for partitioned sfb12 in short blocks.
+     */
+    this.psfb12 = new_float(Encoder.PSFB12);
+    /**
+     * ATH for long block convolution bands.
+     */
+    this.cb_l = new_float(Encoder.CBANDS);
+    /**
+     * ATH for short block convolution bands.
+     */
+    this.cb_s = new_float(Encoder.CBANDS);
+    /**
+     * Equal loudness weights (based on ATH).
+     */
+    this.eql_w = new_float(Encoder.BLKSIZE / 2);
+}
+
+
+function LameGlobalFlags() {
+
+    this.class_id = 0;
+
+    /* input description */
+
+    /**
+     * number of samples. default=-1
+     */
+    this.num_samples = 0;
+    /**
+     * input number of channels. default=2
+     */
+    this.num_channels = 0;
+    /**
+     * input_samp_rate in Hz. default=44.1 kHz
+     */
+    this.in_samplerate = 0;
+    /**
+     * output_samp_rate. default: LAME picks best value at least not used for
+     * MP3 decoding: Remember 44.1 kHz MP3s and AC97
+     */
+    this.out_samplerate = 0;
+    /**
+     * scale input by this amount before encoding at least not used for MP3
+     * decoding
+     */
+    this.scale = 0.;
+    /**
+     * scale input of channel 0 (left) by this amount before encoding
+     */
+    this.scale_left = 0.;
+    /**
+     * scale input of channel 1 (right) by this amount before encoding
+     */
+    this.scale_right = 0.;
+
+    /* general control params */
+    /**
+     * collect data for a MP3 frame analyzer?
+     */
+    this.analysis = false;
+    /**
+     * add Xing VBR tag?
+     */
+    this.bWriteVbrTag = false;
+
+    /**
+     * use lame/mpglib to convert mp3 to wav
+     */
+    this.decode_only = false;
+    /**
+     * quality setting 0=best, 9=worst default=5
+     */
+    this.quality = 0;
+    /**
+     * see enum default = LAME picks best value
+     */
+    this.mode = MPEGMode.STEREO;
+    /**
+     * force M/S mode. requires mode=1
+     */
+    this.force_ms = false;
+    /**
+     * use free format? default=0
+     */
+    this.free_format = false;
+    /**
+     * find the RG value? default=0
+     */
+    this.findReplayGain = false;
+    /**
+     * decode on the fly? default=0
+     */
+    this.decode_on_the_fly = false;
+    /**
+     * 1 (default) writes ID3 tags, 0 not
+     */
+    this.write_id3tag_automatic = false;
+
+    /*
+     * set either brate>0 or compression_ratio>0, LAME will compute the value of
+     * the variable not set. Default is compression_ratio = 11.025
+     */
+    /**
+     * bitrate
+     */
+    this.brate = 0;
+    /**
+     * sizeof(wav file)/sizeof(mp3 file)
+     */
+    this.compression_ratio = 0.;
+
+    /* frame params */
+    /**
+     * mark as copyright. default=0
+     */
+    this.copyright = 0;
+    /**
+     * mark as original. default=1
+     */
+    this.original = 0;
+    /**
+     * the MP3 'private extension' bit. Meaningless
+     */
+    this.extension = 0;
+    /**
+     * Input PCM is emphased PCM (for instance from one of the rarely emphased
+     * CDs), it is STRONGLY not recommended to use this, because psycho does not
+     * take it into account, and last but not least many decoders don't care
+     * about these bits
+     */
+    this.emphasis = 0;
+    /**
+     * use 2 bytes per frame for a CRC checksum. default=0
+     */
+    this.error_protection = 0;
+    /**
+     * enforce ISO spec as much as possible
+     */
+    this.strict_ISO = false;
+
+    /**
+     * use bit reservoir?
+     */
+    this.disable_reservoir = false;
+
+    /* quantization/noise shaping */
+    this.quant_comp = 0;
+    this.quant_comp_short = 0;
+    this.experimentalY = false;
+    this.experimentalZ = 0;
+    this.exp_nspsytune = 0;
+
+    this.preset = 0;
+
+    /* VBR control */
+    this.VBR = null;
+    /**
+     * Range [0,...,1[
+     */
+    this.VBR_q_frac = 0.;
+    /**
+     * Range [0,...,9]
+     */
+    this.VBR_q = 0;
+    this.VBR_mean_bitrate_kbps = 0;
+    this.VBR_min_bitrate_kbps = 0;
+    this.VBR_max_bitrate_kbps = 0;
+    /**
+     * strictly enforce VBR_min_bitrate normaly, it will be violated for analog
+     * silence
+     */
+    this.VBR_hard_min = 0;
+
+    /* resampling and filtering */
+
+    /**
+     * freq in Hz. 0=lame choses. -1=no filter
+     */
+    this.lowpassfreq = 0;
+    /**
+     * freq in Hz. 0=lame choses. -1=no filter
+     */
+    this.highpassfreq = 0;
+    /**
+     * freq width of filter, in Hz (default=15%)
+     */
+    this.lowpasswidth = 0;
+    /**
+     * freq width of filter, in Hz (default=15%)
+     */
+    this.highpasswidth = 0;
+
+    /*
+     * psycho acoustics and other arguments which you should not change unless
+     * you know what you are doing
+     */
+
+    this.maskingadjust = 0.;
+    this.maskingadjust_short = 0.;
+    /**
+     * only use ATH
+     */
+    this.ATHonly = false;
+    /**
+     * only use ATH for short blocks
+     */
+    this.ATHshort = false;
+    /**
+     * disable ATH
+     */
+    this.noATH = false;
+    /**
+     * select ATH formula
+     */
+    this.ATHtype = 0;
+    /**
+     * change ATH formula 4 shape
+     */
+    this.ATHcurve = 0.;
+    /**
+     * lower ATH by this many db
+     */
+    this.ATHlower = 0.;
+    /**
+     * select ATH auto-adjust scheme
+     */
+    this.athaa_type = 0;
+    /**
+     * select ATH auto-adjust loudness calc
+     */
+    this.athaa_loudapprox = 0;
+    /**
+     * dB, tune active region of auto-level
+     */
+    this.athaa_sensitivity = 0.;
+    this.short_blocks = null;
+    /**
+     * use temporal masking effect
+     */
+    this.useTemporal = false;
+    this.interChRatio = 0.;
+    /**
+     * Naoki's adjustment of Mid/Side maskings
+     */
+    this.msfix = 0.;
+
+    /**
+     * 0 off, 1 on
+     */
+    this.tune = false;
+    /**
+     * used to pass values for debugging and stuff
+     */
+    this.tune_value_a = 0.;
+
+    /************************************************************************/
+    /* internal variables, do not set... */
+    /* provided because they may be of use to calling application */
+    /************************************************************************/
+
+    /**
+     * 0=MPEG-2/2.5 1=MPEG-1
+     */
+    this.version = 0;
+    this.encoder_delay = 0;
+    /**
+     * number of samples of padding appended to input
+     */
+    this.encoder_padding = 0;
+    this.framesize = 0;
+    /**
+     * number of frames encoded
+     */
+    this.frameNum = 0;
+    /**
+     * is this struct owned by calling program or lame?
+     */
+    this.lame_allocated_gfp = 0;
+    /**************************************************************************/
+    /* more internal variables are stored in this structure: */
+    /**************************************************************************/
+    this.internal_flags = null;
+}
+
+
+
+function CBRNewIterationLoop(_quantize)  {
+    var quantize = _quantize;
+    this.quantize = quantize;
+	this.iteration_loop = function(gfp, pe, ms_ener_ratio, ratio) {
+		var gfc = gfp.internal_flags;
+        var l3_xmin = new_float(L3Side.SFBMAX);
+		var xrpow = new_float(576);
+		var targ_bits = new_int(2);
+		var mean_bits = 0, max_bits;
+		var l3_side = gfc.l3_side;
+
+		var mb = new MeanBits(mean_bits);
+		this.quantize.rv.ResvFrameBegin(gfp, mb);
+		mean_bits = mb.bits;
+
+		/* quantize! */
+		for (var gr = 0; gr < gfc.mode_gr; gr++) {
+
+			/*
+			 * calculate needed bits
+			 */
+			max_bits = this.quantize.qupvt.on_pe(gfp, pe, targ_bits, mean_bits,
+					gr, gr);
+
+			if (gfc.mode_ext == Encoder.MPG_MD_MS_LR) {
+				abort();//fix cc 精简
+			}
+
+			for (var ch = 0; ch < gfc.channels_out; ch++) {
+				var adjust, masking_lower_db;
+				var cod_info = l3_side.tt[gr][ch];
+
+				if (cod_info.block_type != Encoder.SHORT_TYPE) {
+					// NORM, START or STOP type
+					adjust = 0;
+					masking_lower_db = gfc.PSY.mask_adjust - adjust;
+				} else {
+					adjust = 0;
+					masking_lower_db = gfc.PSY.mask_adjust_short - adjust;
+				}
+				gfc.masking_lower =  Math.pow(10.0,
+						masking_lower_db * 0.1);
+
+				/*
+				 * init_outer_loop sets up cod_info, scalefac and xrpow
+				 */
+				this.quantize.init_outer_loop(gfc, cod_info);
+				if (this.quantize.init_xrpow(gfc, cod_info, xrpow)) {
+					/*
+					 * xr contains energy we will have to encode calculate the
+					 * masking abilities find some good quantization in
+					 * outer_loop
+					 */
+					this.quantize.qupvt.calc_xmin(gfp, ratio[gr][ch], cod_info,
+							l3_xmin);
+					this.quantize.outer_loop(gfp, cod_info, l3_xmin, xrpow, ch,
+							targ_bits[ch]);
+				}
+
+				this.quantize.iteration_finish_one(gfc, gr, ch);
+			} /* for ch */
+		} /* for gr */
+
+		this.quantize.rv.ResvFrameEnd(gfc, mean_bits);
+	}
+}
+
+
+function ReplayGain() {
+	//fix 精简
+}
+
+//package mp3;
+
+/**
+ * Layer III side information.
+ *
+ * @author Ken
+ *
+ */
+
+
+
+function ScaleFac(arrL, arrS, arr21, arr12) {
+
+    this.l = new_int(1 + Encoder.SBMAX_l);
+    this.s = new_int(1 + Encoder.SBMAX_s);
+    this.psfb21 = new_int(1 + Encoder.PSFB21);
+    this.psfb12 = new_int(1 + Encoder.PSFB12);
+    var l = this.l;
+    var s = this.s;
+
+    if (arguments.length == 4) {
+        //public ScaleFac(final int[] arrL, final int[] arrS, final int[] arr21,
+        //    final int[] arr12) {
+        this.arrL = arguments[0];
+        this.arrS = arguments[1];
+        this.arr21 = arguments[2];
+        this.arr12 = arguments[3];
+
+        System.arraycopy(this.arrL, 0, l, 0, Math.min(this.arrL.length, this.l.length));
+        System.arraycopy(this.arrS, 0, s, 0, Math.min(this.arrS.length, this.s.length));
+        System.arraycopy(this.arr21, 0, this.psfb21, 0, Math.min(this.arr21.length, this.psfb21.length));
+        System.arraycopy(this.arr12, 0, this.psfb12, 0, Math.min(this.arr12.length, this.psfb12.length));
+    }
+}
+
+/*
+ *      quantize_pvt source file
+ *
+ *      Copyright (c) 1999-2002 Takehiro Tominaga
+ *      Copyright (c) 2000-2002 Robert Hegemann
+ *      Copyright (c) 2001 Naoki Shibata
+ *      Copyright (c) 2002-2005 Gabriel Bouvigne
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* $Id: QuantizePVT.java,v 1.24 2011/05/24 20:48:06 kenchis Exp $ */
+
+
+QuantizePVT.Q_MAX = (256 + 1);
+QuantizePVT.Q_MAX2 = 116;
+QuantizePVT.LARGE_BITS = 100000;
+QuantizePVT.IXMAX_VAL = 8206;
+
+function QuantizePVT() {
+
+    var tak = null;
+    var rv = null;
+    var psy = null;
+
+    this.setModules = function (_tk, _rv, _psy) {
+        tak = _tk;
+        rv = _rv;
+        psy = _psy;
+    };
+
+    function POW20(x) {
+        return pow20[x + QuantizePVT.Q_MAX2];
+    }
+
+    this.IPOW20 = function (x) {
+        return ipow20[x];
+    }
+
+    /**
+     * smallest such that 1.0+DBL_EPSILON != 1.0
+     */
+    var DBL_EPSILON = 2.2204460492503131e-016;
+
+    /**
+     * ix always <= 8191+15. see count_bits()
+     */
+    var IXMAX_VAL = QuantizePVT.IXMAX_VAL;
+
+    var PRECALC_SIZE = (IXMAX_VAL + 2);
+
+    var Q_MAX = QuantizePVT.Q_MAX;
+
+
+    /**
+     * <CODE>
+     * minimum possible number of
+     * -cod_info.global_gain + ((scalefac[] + (cod_info.preflag ? pretab[sfb] : 0))
+     * << (cod_info.scalefac_scale + 1)) + cod_info.subblock_gain[cod_info.window[sfb]] * 8;
+     *
+     * for long block, 0+((15+3)<<2) = 18*4 = 72
+     * for short block, 0+(15<<2)+7*8 = 15*4+56 = 116
+     * </CODE>
+     */
+    var Q_MAX2 = QuantizePVT.Q_MAX2;
+
+    var LARGE_BITS = QuantizePVT.LARGE_BITS;
+
+
+    /**
+     * Assuming dynamic range=96dB, this value should be 92
+     */
+    var NSATHSCALE = 100;
+
+    /**
+     * The following table is used to implement the scalefactor partitioning for
+     * MPEG2 as described in section 2.4.3.2 of the IS. The indexing corresponds
+     * to the way the tables are presented in the IS:
+     *
+     * [table_number][row_in_table][column of nr_of_sfb]
+     */
+    this.nr_of_sfb_block = [
+        [[6, 5, 5, 5], [9, 9, 9, 9], [6, 9, 9, 9]],
+        [[6, 5, 7, 3], [9, 9, 12, 6], [6, 9, 12, 6]],
+        [[11, 10, 0, 0], [18, 18, 0, 0], [15, 18, 0, 0]],
+        [[7, 7, 7, 0], [12, 12, 12, 0], [6, 15, 12, 0]],
+        [[6, 6, 6, 3], [12, 9, 9, 6], [6, 12, 9, 6]],
+        [[8, 8, 5, 0], [15, 12, 9, 0], [6, 18, 9, 0]]];
+
+    /**
+     * Table B.6: layer3 preemphasis
+     */
+    var pretab = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
+        2, 2, 3, 3, 3, 2, 0];
+    this.pretab = pretab;
+
+    /**
+     * Here are MPEG1 Table B.8 and MPEG2 Table B.1 -- Layer III scalefactor
+     * bands. <BR>
+     * Index into this using a method such as:<BR>
+     * idx = fr_ps.header.sampling_frequency + (fr_ps.header.version * 3)
+     */
+    this.sfBandIndex = [
+        // Table B.2.b: 22.05 kHz
+        new ScaleFac([0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464,
+                522, 576],
+            [0, 4, 8, 12, 18, 24, 32, 42, 56, 74, 100, 132, 174, 192]
+            , [0, 0, 0, 0, 0, 0, 0] //  sfb21 pseudo sub bands
+            , [0, 0, 0, 0, 0, 0, 0] //  sfb12 pseudo sub bands
+        ),
+        /* Table B.2.c: 24 kHz */ /* docs: 332. mpg123(broken): 330 */
+        new ScaleFac([0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 114, 136, 162, 194, 232, 278, 332, 394, 464,
+                540, 576],
+            [0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 136, 180, 192]
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb21 pseudo sub bands */
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb12 pseudo sub bands */
+        ),
+        /* Table B.2.a: 16 kHz */
+        new ScaleFac([0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464,
+                522, 576],
+            [0, 4, 8, 12, 18, 26, 36, 48, 62, 80, 104, 134, 174, 192]
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb21 pseudo sub bands */
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb12 pseudo sub bands */
+        ),
+        /* Table B.8.b: 44.1 kHz */
+        new ScaleFac([0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 52, 62, 74, 90, 110, 134, 162, 196, 238, 288, 342, 418,
+                576],
+            [0, 4, 8, 12, 16, 22, 30, 40, 52, 66, 84, 106, 136, 192]
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb21 pseudo sub bands */
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb12 pseudo sub bands */
+        ),
+        /* Table B.8.c: 48 kHz */
+        new ScaleFac([0, 4, 8, 12, 16, 20, 24, 30, 36, 42, 50, 60, 72, 88, 106, 128, 156, 190, 230, 276, 330, 384,
+                576],
+            [0, 4, 8, 12, 16, 22, 28, 38, 50, 64, 80, 100, 126, 192]
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb21 pseudo sub bands */
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb12 pseudo sub bands */
+        ),
+        /* Table B.8.a: 32 kHz */
+        new ScaleFac([0, 4, 8, 12, 16, 20, 24, 30, 36, 44, 54, 66, 82, 102, 126, 156, 194, 240, 296, 364, 448, 550,
+                576],
+            [0, 4, 8, 12, 16, 22, 30, 42, 58, 78, 104, 138, 180, 192]
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb21 pseudo sub bands */
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb12 pseudo sub bands */
+        ),
+        /* MPEG-2.5 11.025 kHz */
+        new ScaleFac([0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464,
+                522, 576],
+            [0 / 3, 12 / 3, 24 / 3, 36 / 3, 54 / 3, 78 / 3, 108 / 3, 144 / 3, 186 / 3, 240 / 3, 312 / 3,
+                402 / 3, 522 / 3, 576 / 3]
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb21 pseudo sub bands */
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb12 pseudo sub bands */
+        ),
+        /* MPEG-2.5 12 kHz */
+        new ScaleFac([0, 6, 12, 18, 24, 30, 36, 44, 54, 66, 80, 96, 116, 140, 168, 200, 238, 284, 336, 396, 464,
+                522, 576],
+            [0 / 3, 12 / 3, 24 / 3, 36 / 3, 54 / 3, 78 / 3, 108 / 3, 144 / 3, 186 / 3, 240 / 3, 312 / 3,
+                402 / 3, 522 / 3, 576 / 3]
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb21 pseudo sub bands */
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb12 pseudo sub bands */
+        ),
+        /* MPEG-2.5 8 kHz */
+        new ScaleFac([0, 12, 24, 36, 48, 60, 72, 88, 108, 132, 160, 192, 232, 280, 336, 400, 476, 566, 568, 570,
+                572, 574, 576],
+            [0 / 3, 24 / 3, 48 / 3, 72 / 3, 108 / 3, 156 / 3, 216 / 3, 288 / 3, 372 / 3, 480 / 3, 486 / 3,
+                492 / 3, 498 / 3, 576 / 3]
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb21 pseudo sub bands */
+            , [0, 0, 0, 0, 0, 0, 0] /*  sfb12 pseudo sub bands */
+        )
+    ];
+
+    var pow20 = new_float(Q_MAX + Q_MAX2 + 1);
+    var ipow20 = new_float(Q_MAX);
+    var pow43 = new_float(PRECALC_SIZE);
+
+    var adj43 = new_float(PRECALC_SIZE);
+    this.adj43 = adj43;
+
+    /**
+     * <PRE>
+     * compute the ATH for each scalefactor band cd range: 0..96db
+     *
+     * Input: 3.3kHz signal 32767 amplitude (3.3kHz is where ATH is smallest =
+     * -5db) longblocks: sfb=12 en0/bw=-11db max_en0 = 1.3db shortblocks: sfb=5
+     * -9db 0db
+     *
+     * Input: 1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 -1 (repeated) longblocks: amp=1
+     * sfb=12 en0/bw=-103 db max_en0 = -92db amp=32767 sfb=12 -12 db -1.4db
+     *
+     * Input: 1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 -1 (repeated) shortblocks: amp=1
+     * sfb=5 en0/bw= -99 -86 amp=32767 sfb=5 -9 db 4db
+     *
+     *
+     * MAX energy of largest wave at 3.3kHz = 1db AVE energy of largest wave at
+     * 3.3kHz = -11db Let's take AVE: -11db = maximum signal in sfb=12. Dynamic
+     * range of CD: 96db. Therefor energy of smallest audible wave in sfb=12 =
+     * -11 - 96 = -107db = ATH at 3.3kHz.
+     *
+     * ATH formula for this wave: -5db. To adjust to LAME scaling, we need ATH =
+     * ATH_formula - 103 (db) ATH = ATH * 2.5e-10 (ener)
+     * </PRE>
+     */
+    function ATHmdct(gfp, f) {
+        var ath = psy.ATHformula(f, gfp);
+
+        ath -= NSATHSCALE;
+
+        /* modify the MDCT scaling for the ATH and convert to energy */
+        ath = Math.pow(10.0, ath / 10.0 + gfp.ATHlower);
+        return ath;
+    }
+
+    function compute_ath(gfp) {
+        var ATH_l = gfp.internal_flags.ATH.l;
+        var ATH_psfb21 = gfp.internal_flags.ATH.psfb21;
+        var ATH_s = gfp.internal_flags.ATH.s;
+        var ATH_psfb12 = gfp.internal_flags.ATH.psfb12;
+        var gfc = gfp.internal_flags;
+        var samp_freq = gfp.out_samplerate;
+
+        for (var sfb = 0; sfb < Encoder.SBMAX_l; sfb++) {
+            var start = gfc.scalefac_band.l[sfb];
+            var end = gfc.scalefac_band.l[sfb + 1];
+            ATH_l[sfb] = Float.MAX_VALUE;
+            for (var i = start; i < end; i++) {
+                var freq = i * samp_freq / (2 * 576);
+                var ATH_f = ATHmdct(gfp, freq);
+                /* freq in kHz */
+                ATH_l[sfb] = Math.min(ATH_l[sfb], ATH_f);
+            }
+        }
+
+        for (var sfb = 0; sfb < Encoder.PSFB21; sfb++) {
+            var start = gfc.scalefac_band.psfb21[sfb];
+            var end = gfc.scalefac_band.psfb21[sfb + 1];
+            ATH_psfb21[sfb] = Float.MAX_VALUE;
+            for (var i = start; i < end; i++) {
+                var freq = i * samp_freq / (2 * 576);
+                var ATH_f = ATHmdct(gfp, freq);
+                /* freq in kHz */
+                ATH_psfb21[sfb] = Math.min(ATH_psfb21[sfb], ATH_f);
+            }
+        }
+
+        for (var sfb = 0; sfb < Encoder.SBMAX_s; sfb++) {
+            var start = gfc.scalefac_band.s[sfb];
+            var end = gfc.scalefac_band.s[sfb + 1];
+            ATH_s[sfb] = Float.MAX_VALUE;
+            for (var i = start; i < end; i++) {
+                var freq = i * samp_freq / (2 * 192);
+                var ATH_f = ATHmdct(gfp, freq);
+                /* freq in kHz */
+                ATH_s[sfb] = Math.min(ATH_s[sfb], ATH_f);
+            }
+            ATH_s[sfb] *= (gfc.scalefac_band.s[sfb + 1] - gfc.scalefac_band.s[sfb]);
+        }
+
+        for (var sfb = 0; sfb < Encoder.PSFB12; sfb++) {
+            var start = gfc.scalefac_band.psfb12[sfb];
+            var end = gfc.scalefac_band.psfb12[sfb + 1];
+            ATH_psfb12[sfb] = Float.MAX_VALUE;
+            for (var i = start; i < end; i++) {
+                var freq = i * samp_freq / (2 * 192);
+                var ATH_f = ATHmdct(gfp, freq);
+                /* freq in kHz */
+                ATH_psfb12[sfb] = Math.min(ATH_psfb12[sfb], ATH_f);
+            }
+            /* not sure about the following */
+            ATH_psfb12[sfb] *= (gfc.scalefac_band.s[13] - gfc.scalefac_band.s[12]);
+        }
+
+        /*
+         * no-ATH mode: reduce ATH to -200 dB
+         */
+        if (gfp.noATH) {
+            abort();//fix cc 精简
+        }
+
+        /*
+         * work in progress, don't rely on it too much
+         */
+        gfc.ATH.floor = 10. * Math_log10(ATHmdct(gfp, -1.));
+    }
+
+    /**
+     * initialization for iteration_loop
+     */
+    this.iteration_init = function (gfp) {
+        var gfc = gfp.internal_flags;
+        var l3_side = gfc.l3_side;
+        var i;
+
+        if (gfc.iteration_init_init == 0) {
+            gfc.iteration_init_init = 1;
+
+            l3_side.main_data_begin = 0;
+            compute_ath(gfp);
+
+            pow43[0] = 0.0;
+            for (i = 1; i < PRECALC_SIZE; i++)
+                pow43[i] = Math.pow(i, 4.0 / 3.0);
+
+            for (i = 0; i < PRECALC_SIZE - 1; i++)
+                adj43[i] = ((i + 1) - Math.pow(
+                    0.5 * (pow43[i] + pow43[i + 1]), 0.75));
+            adj43[i] = 0.5;
+
+            for (i = 0; i < Q_MAX; i++)
+                ipow20[i] = Math.pow(2.0, (i - 210) * -0.1875);
+            for (i = 0; i <= Q_MAX + Q_MAX2; i++)
+                pow20[i] = Math.pow(2.0, (i - 210 - Q_MAX2) * 0.25);
+
+            tak.huffman_init(gfc);
+
+            {
+                var bass, alto, treble, sfb21;
+
+                i = (gfp.exp_nspsytune >> 2) & 63;
+                if (i >= 32)
+                    i -= 64;
+                bass = Math.pow(10, i / 4.0 / 10.0);
+
+                i = (gfp.exp_nspsytune >> 8) & 63;
+                if (i >= 32)
+                    i -= 64;
+                alto = Math.pow(10, i / 4.0 / 10.0);
+
+                i = (gfp.exp_nspsytune >> 14) & 63;
+                if (i >= 32)
+                    i -= 64;
+                treble = Math.pow(10, i / 4.0 / 10.0);
+
+                /*
+                 * to be compatible with Naoki's original code, the next 6 bits
+                 * define only the amount of changing treble for sfb21
+                 */
+                i = (gfp.exp_nspsytune >> 20) & 63;
+                if (i >= 32)
+                    i -= 64;
+                sfb21 = treble * Math.pow(10, i / 4.0 / 10.0);
+                for (i = 0; i < Encoder.SBMAX_l; i++) {
+                    var f;
+                    if (i <= 6)
+                        f = bass;
+                    else if (i <= 13)
+                        f = alto;
+                    else if (i <= 20)
+                        f = treble;
+                    else
+                        f = sfb21;
+
+                    gfc.nsPsy.longfact[i] = f;
+                }
+                for (i = 0; i < Encoder.SBMAX_s; i++) {
+                    var f;
+                    if (i <= 5)
+                        f = bass;
+                    else if (i <= 10)
+                        f = alto;
+                    else if (i <= 11)
+                        f = treble;
+                    else
+                        f = sfb21;
+
+                    gfc.nsPsy.shortfact[i] = f;
+                }
+            }
+        }
+    }
+
+    /**
+     * allocate bits among 2 channels based on PE<BR>
+     * mt 6/99<BR>
+     * bugfixes rh 8/01: often allocated more than the allowed 4095 bits
+     */
+    this.on_pe = function (gfp, pe,
+                           targ_bits, mean_bits, gr, cbr) {
+        var gfc = gfp.internal_flags;
+        var tbits = 0, bits;
+        var add_bits = new_int(2);
+        var ch;
+
+        /* allocate targ_bits for granule */
+        var mb = new MeanBits(tbits);
+        var extra_bits = rv.ResvMaxBits(gfp, mean_bits, mb, cbr);
+        tbits = mb.bits;
+        /* maximum allowed bits for this granule */
+        var max_bits = tbits + extra_bits;
+        if (max_bits > LameInternalFlags.MAX_BITS_PER_GRANULE) {
+            // hard limit per granule
+            max_bits = LameInternalFlags.MAX_BITS_PER_GRANULE;
+        }
+        for (bits = 0, ch = 0; ch < gfc.channels_out; ++ch) {
+            /******************************************************************
+             * allocate bits for each channel
+             ******************************************************************/
+            targ_bits[ch] = Math.min(LameInternalFlags.MAX_BITS_PER_CHANNEL,
+                tbits / gfc.channels_out);
+
+            add_bits[ch] = 0 | (targ_bits[ch] * pe[gr][ch] / 700.0 - targ_bits[ch]);
+
+            /* at most increase bits by 1.5*average */
+            if (add_bits[ch] > mean_bits * 3 / 4)
+                add_bits[ch] = mean_bits * 3 / 4;
+
+            if (add_bits[ch] < 0)
+                add_bits[ch] = 0;
+
+            if (add_bits[ch] + targ_bits[ch] > LameInternalFlags.MAX_BITS_PER_CHANNEL)
+                add_bits[ch] = Math.max(0,
+                    LameInternalFlags.MAX_BITS_PER_CHANNEL - targ_bits[ch]);
+
+            bits += add_bits[ch];
+        }
+        if (bits > extra_bits) {
+            for (ch = 0; ch < gfc.channels_out; ++ch) {
+                add_bits[ch] = extra_bits * add_bits[ch] / bits;
+            }
+        }
+
+        for (ch = 0; ch < gfc.channels_out; ++ch) {
+            targ_bits[ch] += add_bits[ch];
+            extra_bits -= add_bits[ch];
+        }
+
+        for (bits = 0, ch = 0; ch < gfc.channels_out; ++ch) {
+            bits += targ_bits[ch];
+        }
+        if (bits > LameInternalFlags.MAX_BITS_PER_GRANULE) {
+            abort();//fix cc 精简
+        }
+
+        return max_bits;
+    }
+
+    //fix cc 精简
+	
+    /**
+     *  Robert Hegemann 2001-04-27:
+     *  this adjusts the ATH, keeping the original noise floor
+     *  affects the higher frequencies more than the lower ones
+     */
+    this.athAdjust = function (a, x, athFloor) {
+        /*
+         * work in progress
+         */
+        var o = 90.30873362;
+        var p = 94.82444863;
+        var u = Util.FAST_LOG10_X(x, 10.0);
+        var v = a * a;
+        var w = 0.0;
+        u -= athFloor;
+        /* undo scaling */
+        if (v > 1E-20)
+            w = 1. + Util.FAST_LOG10_X(v, 10.0 / o);
+        if (w < 0)
+            w = 0.;
+        u *= w;
+        u += athFloor + o - p;
+        /* redo scaling */
+
+        return Math.pow(10., 0.1 * u);
+    };
+
+    /**
+     * Calculate the allowed distortion for each scalefactor band, as determined
+     * by the psychoacoustic model. xmin(sb) = ratio(sb) * en(sb) / bw(sb)
+     *
+     * returns number of sfb's with energy > ATH
+     */
+    this.calc_xmin = function (gfp, ratio, cod_info, pxmin) {
+        var pxminPos = 0;
+        var gfc = gfp.internal_flags;
+        var gsfb, j = 0, ath_over = 0;
+        var ATH = gfc.ATH;
+        var xr = cod_info.xr;
+        var enable_athaa_fix = (gfp.VBR == VbrMode.vbr_mtrh) ? 1 : 0;
+        var masking_lower = gfc.masking_lower;
+
+        if (gfp.VBR == VbrMode.vbr_mtrh || gfp.VBR == VbrMode.vbr_mt) {
+            /* was already done in PSY-Model */
+            masking_lower = 1.0;
+        }
+
+        for (gsfb = 0; gsfb < cod_info.psy_lmax; gsfb++) {
+            var en0, xmin;
+            var rh1, rh2;
+            var width, l;
+
+            if (gfp.VBR == VbrMode.vbr_rh || gfp.VBR == VbrMode.vbr_mtrh)
+                xmin = athAdjust(ATH.adjust, ATH.l[gsfb], ATH.floor);
+            else
+                xmin = ATH.adjust * ATH.l[gsfb];
+
+            width = cod_info.width[gsfb];
+            rh1 = xmin / width;
+            rh2 = DBL_EPSILON;
+            l = width >> 1;
+            en0 = 0.0;
+            do {
+                var xa, xb;
+                xa = xr[j] * xr[j];
+                en0 += xa;
+                rh2 += (xa < rh1) ? xa : rh1;
+                j++;
+                xb = xr[j] * xr[j];
+                en0 += xb;
+                rh2 += (xb < rh1) ? xb : rh1;
+                j++;
+            } while (--l > 0);
+            if (en0 > xmin)
+                ath_over++;
+
+            if (gsfb == Encoder.SBPSY_l) {
+                abort();//fix cc 精简
+            }
+            if (enable_athaa_fix != 0) {
+                xmin = rh2;
+            }
+            if (!gfp.ATHonly) {
+                var e = ratio.en.l[gsfb];
+                if (e > 0.0) {
+                    var x;
+                    x = en0 * ratio.thm.l[gsfb] * masking_lower / e;
+                    if (enable_athaa_fix != 0)
+                        x *= gfc.nsPsy.longfact[gsfb];
+                    if (xmin < x)
+                        xmin = x;
+                }
+            }
+            if (enable_athaa_fix != 0)
+                pxmin[pxminPos++] = xmin;
+            else
+                pxmin[pxminPos++] = xmin * gfc.nsPsy.longfact[gsfb];
+        }
+        /* end of long block loop */
+
+        /* use this function to determine the highest non-zero coeff */
+        var max_nonzero = 575;
+        if (cod_info.block_type != Encoder.SHORT_TYPE) {
+            // NORM, START or STOP type, but not SHORT
+            var k = 576;
+            while (k-- != 0 && BitStream.EQ(xr[k], 0)) {
+                max_nonzero = k;
+            }
+        }
+        cod_info.max_nonzero_coeff = max_nonzero;
+
+        for (var sfb = cod_info.sfb_smin; gsfb < cod_info.psymax; sfb++, gsfb += 3) {
+            var width, b;
+            var tmpATH;
+            if (gfp.VBR == VbrMode.vbr_rh || gfp.VBR == VbrMode.vbr_mtrh)
+                tmpATH = athAdjust(ATH.adjust, ATH.s[sfb], ATH.floor);
+            else
+                tmpATH = ATH.adjust * ATH.s[sfb];
+
+            width = cod_info.width[gsfb];
+            for (b = 0; b < 3; b++) {
+                var en0 = 0.0, xmin;
+                var rh1, rh2;
+                var l = width >> 1;
+
+                rh1 = tmpATH / width;
+                rh2 = DBL_EPSILON;
+                do {
+                    var xa, xb;
+                    xa = xr[j] * xr[j];
+                    en0 += xa;
+                    rh2 += (xa < rh1) ? xa : rh1;
+                    j++;
+                    xb = xr[j] * xr[j];
+                    en0 += xb;
+                    rh2 += (xb < rh1) ? xb : rh1;
+                    j++;
+                } while (--l > 0);
+                if (en0 > tmpATH)
+                    ath_over++;
+                if (sfb == Encoder.SBPSY_s) {
+                    abort();//fix cc 精简
+                }
+                if (enable_athaa_fix != 0)
+                    xmin = rh2;
+                else
+                    xmin = tmpATH;
+
+                if (!gfp.ATHonly && !gfp.ATHshort) {
+                    var e = ratio.en.s[sfb][b];
+                    if (e > 0.0) {
+                        var x;
+                        x = en0 * ratio.thm.s[sfb][b] * masking_lower / e;
+                        if (enable_athaa_fix != 0)
+                            x *= gfc.nsPsy.shortfact[sfb];
+                        if (xmin < x)
+                            xmin = x;
+                    }
+                }
+                if (enable_athaa_fix != 0)
+                    pxmin[pxminPos++] = xmin;
+                else
+                    pxmin[pxminPos++] = xmin * gfc.nsPsy.shortfact[sfb];
+            }
+            /* b */
+            if (gfp.useTemporal) {
+                if (pxmin[pxminPos - 3] > pxmin[pxminPos - 3 + 1])
+                    pxmin[pxminPos - 3 + 1] += (pxmin[pxminPos - 3] - pxmin[pxminPos - 3 + 1])
+                        * gfc.decay;
+                if (pxmin[pxminPos - 3 + 1] > pxmin[pxminPos - 3 + 2])
+                    pxmin[pxminPos - 3 + 2] += (pxmin[pxminPos - 3 + 1] - pxmin[pxminPos - 3 + 2])
+                        * gfc.decay;
+            }
+        }
+        /* end of short block sfb loop */
+
+        return ath_over;
+    };
+
+    function StartLine(j) {
+        this.s = j;
+    }
+
+    this.calc_noise_core = function (cod_info, startline, l, step) {
+        var noise = 0;
+        var j = startline.s;
+        var ix = cod_info.l3_enc;
+
+        if (j > cod_info.count1) {
+            while ((l--) != 0) {
+                var temp;
+                temp = cod_info.xr[j];
+                j++;
+                noise += temp * temp;
+                temp = cod_info.xr[j];
+                j++;
+                noise += temp * temp;
+            }
+        } else if (j > cod_info.big_values) {
+            var ix01 = new_float(2);
+            ix01[0] = 0;
+            ix01[1] = step;
+            while ((l--) != 0) {
+                var temp;
+                temp = Math.abs(cod_info.xr[j]) - ix01[ix[j]];
+                j++;
+                noise += temp * temp;
+                temp = Math.abs(cod_info.xr[j]) - ix01[ix[j]];
+                j++;
+                noise += temp * temp;
+            }
+        } else {
+            while ((l--) != 0) {
+                var temp;
+                temp = Math.abs(cod_info.xr[j]) - pow43[ix[j]] * step;
+                j++;
+                noise += temp * temp;
+                temp = Math.abs(cod_info.xr[j]) - pow43[ix[j]] * step;
+                j++;
+                noise += temp * temp;
+            }
+        }
+
+        startline.s = j;
+        return noise;
+    }
+
+    /**
+     * <PRE>
+     * -oo dB  =>  -1.00
+     * - 6 dB  =>  -0.97
+     * - 3 dB  =>  -0.80
+     * - 2 dB  =>  -0.64
+     * - 1 dB  =>  -0.38
+     *   0 dB  =>   0.00
+     * + 1 dB  =>  +0.49
+     * + 2 dB  =>  +1.06
+     * + 3 dB  =>  +1.68
+     * + 6 dB  =>  +3.69
+     * +10 dB  =>  +6.45
+     * </PRE>
+     */
+    this.calc_noise = function (cod_info, l3_xmin, distort, res, prev_noise) {
+        var distortPos = 0;
+        var l3_xminPos = 0;
+        var sfb, l, over = 0;
+        var over_noise_db = 0;
+        /* 0 dB relative to masking */
+        var tot_noise_db = 0;
+        /* -200 dB relative to masking */
+        var max_noise = -20.0;
+        var j = 0;
+        var scalefac = cod_info.scalefac;
+        var scalefacPos = 0;
+
+        res.over_SSD = 0;
+
+        for (sfb = 0; sfb < cod_info.psymax; sfb++) {
+            var s = cod_info.global_gain
+                - (((scalefac[scalefacPos++]) + (cod_info.preflag != 0 ? pretab[sfb]
+                    : 0)) << (cod_info.scalefac_scale + 1))
+                - cod_info.subblock_gain[cod_info.window[sfb]] * 8;
+            var noise = 0.0;
+
+            if (prev_noise != null && (prev_noise.step[sfb] == s)) {
+
+                /* use previously computed values */
+                noise = prev_noise.noise[sfb];
+                j += cod_info.width[sfb];
+                distort[distortPos++] = noise / l3_xmin[l3_xminPos++];
+
+                noise = prev_noise.noise_log[sfb];
+
+            } else {
+                var step = POW20(s);
+                l = cod_info.width[sfb] >> 1;
+
+                if ((j + cod_info.width[sfb]) > cod_info.max_nonzero_coeff) {
+                    var usefullsize;
+                    usefullsize = cod_info.max_nonzero_coeff - j + 1;
+
+                    if (usefullsize > 0)
+                        l = usefullsize >> 1;
+                    else
+                        l = 0;
+                }
+
+                var sl = new StartLine(j);
+                noise = this.calc_noise_core(cod_info, sl, l, step);
+                j = sl.s;
+
+                if (prev_noise != null) {
+                    /* save noise values */
+                    prev_noise.step[sfb] = s;
+                    prev_noise.noise[sfb] = noise;
+                }
+
+                noise = distort[distortPos++] = noise / l3_xmin[l3_xminPos++];
+
+                /* multiplying here is adding in dB, but can overflow */
+                noise = Util.FAST_LOG10(Math.max(noise, 1E-20));
+
+                if (prev_noise != null) {
+                    /* save noise values */
+                    prev_noise.noise_log[sfb] = noise;
+                }
+            }
+
+            if (prev_noise != null) {
+                /* save noise values */
+                prev_noise.global_gain = cod_info.global_gain;
+            }
+
+            tot_noise_db += noise;
+
+            if (noise > 0.0) {
+                var tmp;
+
+                tmp = Math.max(0 | (noise * 10 + .5), 1);
+                res.over_SSD += tmp * tmp;
+
+                over++;
+                /* multiplying here is adding in dB -but can overflow */
+                /* over_noise *= noise; */
+                over_noise_db += noise;
+            }
+            max_noise = Math.max(max_noise, noise);
+
+        }
+
+        res.over_count = over;
+        res.tot_noise = tot_noise_db;
+        res.over_noise = over_noise_db;
+        res.max_noise = max_noise;
+
+        return over;
+    }
+
+    //fix cc 精简
+
+}
+
+
+function CalcNoiseData() {
+    this.global_gain = 0;
+    this.sfb_count1 = 0;
+    this.step = new_int(39);
+    this.noise = new_float(39);
+    this.noise_log = new_float(39);
+}
+
+//package mp3;
+
+
+function GrInfo() {
+    //float xr[] = new float[576];
+    this.xr = new_float(576);
+    //int l3_enc[] = new int[576];
+    this.l3_enc = new_int(576);
+    //int scalefac[] = new int[L3Side.SFBMAX];
+    this.scalefac = new_int(L3Side.SFBMAX);
+    this.xrpow_max = 0.;
+
+    this.part2_3_length = 0;
+    this.big_values = 0;
+    this.count1 = 0;
+    this.global_gain = 0;
+    this.scalefac_compress = 0;
+    this.block_type = 0;
+    this.mixed_block_flag = 0;
+    this.table_select = new_int(3);
+    this.subblock_gain = new_int(3 + 1);
+    this.region0_count = 0;
+    this.region1_count = 0;
+    this.preflag = 0;
+    this.scalefac_scale = 0;
+    this.count1table_select = 0;
+
+    this.part2_length = 0;
+    this.sfb_lmax = 0;
+    this.sfb_smin = 0;
+    this.psy_lmax = 0;
+    this.sfbmax = 0;
+    this.psymax = 0;
+    this.sfbdivide = 0;
+    this.width = new_int(L3Side.SFBMAX);
+    this.window = new_int(L3Side.SFBMAX);
+    this.count1bits = 0;
+    /**
+     * added for LSF
+     */
+    this.sfb_partition_table = null;
+    this.slen = new_int(4);
+
+    this.max_nonzero_coeff = 0;
+
+    var self = this;
+    function clone_int(array) {
+        return new Int32Array(array);
+    }
+    function clone_float(array) {
+        return new Float32Array(array);
+    }
+    this.assign = function (other) {
+        self.xr = clone_float(other.xr); //.slice(0); //clone();
+        self.l3_enc = clone_int(other.l3_enc); //.slice(0); //clone();
+        self.scalefac = clone_int(other.scalefac);//.slice(0); //clone();
+        self.xrpow_max = other.xrpow_max;
+
+        self.part2_3_length = other.part2_3_length;
+        self.big_values = other.big_values;
+        self.count1 = other.count1;
+        self.global_gain = other.global_gain;
+        self.scalefac_compress = other.scalefac_compress;
+        self.block_type = other.block_type;
+        self.mixed_block_flag = other.mixed_block_flag;
+        self.table_select = clone_int(other.table_select);//.slice(0); //clone();
+        self.subblock_gain = clone_int(other.subblock_gain); //.slice(0); //.clone();
+        self.region0_count = other.region0_count;
+        self.region1_count = other.region1_count;
+        self.preflag = other.preflag;
+        self.scalefac_scale = other.scalefac_scale;
+        self.count1table_select = other.count1table_select;
+
+        self.part2_length = other.part2_length;
+        self.sfb_lmax = other.sfb_lmax;
+        self.sfb_smin = other.sfb_smin;
+        self.psy_lmax = other.psy_lmax;
+        self.sfbmax = other.sfbmax;
+        self.psymax = other.psymax;
+        self.sfbdivide = other.sfbdivide;
+        self.width = clone_int(other.width); //.slice(0); //.clone();
+        self.window = clone_int(other.window); //.slice(0); //.clone();
+        self.count1bits = other.count1bits;
+
+        self.sfb_partition_table = other.sfb_partition_table.slice(0); //.clone();
+        self.slen = clone_int(other.slen); //.slice(0); //.clone();
+        self.max_nonzero_coeff = other.max_nonzero_coeff;
+    }
+}
+
+
+var L3Side = {};
+
+
+	/**
+	 * max scalefactor band, max(SBMAX_l, SBMAX_s*3, (SBMAX_s-3)*3+8)
+	 */
+L3Side.SFBMAX = (Encoder.SBMAX_s * 3);
+
+/*
+ * MP3 quantization
+ *
+ *      Copyright (c) 1999-2000 Mark Taylor
+ *      Copyright (c) 1999-2003 Takehiro Tominaga
+ *      Copyright (c) 2000-2007 Robert Hegemann
+ *      Copyright (c) 2001-2005 Gabriel Bouvigne
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* $Id: Quantize.java,v 1.24 2011/05/24 20:48:06 kenchis Exp $ */
+
+//package mp3;
+
+//import java.util.Arrays;
+
+
+function Quantize() {
+    var bs;
+    this.rv = null;
+    var rv;
+    this.qupvt = null;
+    var qupvt;
+
+    var vbr = new VBRQuantize();
+    var tk;
+
+    this.setModules = function (_bs, _rv, _qupvt, _tk) {
+        bs = _bs;
+        rv = _rv;
+        this.rv = _rv;
+        qupvt = _qupvt;
+        this.qupvt = _qupvt;
+        tk = _tk;
+        vbr.setModules(qupvt, tk);
+    }
+
+    //fix cc 精简
+
+    /**
+     * mt 6/99
+     *
+     * initializes cod_info, scalefac and xrpow
+     *
+     * returns 0 if all energies in xr are zero, else 1
+     */
+    function init_xrpow_core(cod_info, xrpow, upper, sum) {
+        sum = 0;
+        for (var i = 0; i <= upper; ++i) {
+            var tmp = Math.abs(cod_info.xr[i]);
+            sum += tmp;
+            xrpow[i] = Math.sqrt(tmp * Math.sqrt(tmp));
+
+            if (xrpow[i] > cod_info.xrpow_max)
+                cod_info.xrpow_max = xrpow[i];
+        }
+        return sum;
+    }
+
+    this.init_xrpow = function (gfc, cod_info, xrpow) {
+        var sum = 0;
+        var upper = 0 | cod_info.max_nonzero_coeff;
+
+        cod_info.xrpow_max = 0;
+
+        /*
+         * check if there is some energy we have to quantize and calculate xrpow
+         * matching our fresh scalefactors
+         */
+
+        Arrays.fill(xrpow, upper, 576, 0);
+
+        sum = init_xrpow_core(cod_info, xrpow, upper, sum);
+
+        /*
+         * return 1 if we have something to quantize, else 0
+         */
+        if (sum > 1E-20) {
+            var j = 0;
+            if ((gfc.substep_shaping & 2) != 0)
+                j = 1;
+
+            for (var i = 0; i < cod_info.psymax; i++)
+                gfc.pseudohalf[i] = j;
+
+            return true;
+        }
+
+        Arrays.fill(cod_info.l3_enc, 0, 576, 0);
+        return false;
+    }
+
+    /**
+     * Gabriel Bouvigne feb/apr 2003<BR>
+     * Analog silence detection in partitionned sfb21 or sfb12 for short blocks
+     *
+     * From top to bottom of sfb, changes to 0 coeffs which are below ath. It
+     * stops on the first coeff higher than ath.
+     */
+    function psfb21_analogsilence(gfc, cod_info) {
+        var ath = gfc.ATH;
+        var xr = cod_info.xr;
+
+        if (cod_info.block_type != Encoder.SHORT_TYPE) {
+            /* NORM, START or STOP type, but not SHORT blocks */
+            var stop = false;
+            for (var gsfb = Encoder.PSFB21 - 1; gsfb >= 0 && !stop; gsfb--) {
+                var start = gfc.scalefac_band.psfb21[gsfb];
+                var end = gfc.scalefac_band.psfb21[gsfb + 1];
+                var ath21 = qupvt.athAdjust(ath.adjust, ath.psfb21[gsfb],
+                    ath.floor);
+
+                if (gfc.nsPsy.longfact[21] > 1e-12)
+                    ath21 *= gfc.nsPsy.longfact[21];
+
+                for (var j = end - 1; j >= start; j--) {
+                    if (Math.abs(xr[j]) < ath21)
+                        xr[j] = 0;
+                    else {
+                        stop = true;
+                        break;
+                    }
+                }
+            }
+        } else {
+            /* note: short blocks coeffs are reordered */
+            for (var block = 0; block < 3; block++) {
+                var stop = false;
+                for (var gsfb = Encoder.PSFB12 - 1; gsfb >= 0 && !stop; gsfb--) {
+                    var start = gfc.scalefac_band.s[12]
+                        * 3
+                        + (gfc.scalefac_band.s[13] - gfc.scalefac_band.s[12])
+                        * block
+                        + (gfc.scalefac_band.psfb12[gsfb] - gfc.scalefac_band.psfb12[0]);
+                    var end = start
+                        + (gfc.scalefac_band.psfb12[gsfb + 1] - gfc.scalefac_band.psfb12[gsfb]);
+                    var ath12 = qupvt.athAdjust(ath.adjust, ath.psfb12[gsfb],
+                        ath.floor);
+
+                    if (gfc.nsPsy.shortfact[12] > 1e-12)
+                        ath12 *= gfc.nsPsy.shortfact[12];
+
+                    for (var j = end - 1; j >= start; j--) {
+                        if (Math.abs(xr[j]) < ath12)
+                            xr[j] = 0;
+                        else {
+                            stop = true;
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+    this.init_outer_loop = function (gfc, cod_info) {
+        /*
+         * initialize fresh cod_info
+         */
+        cod_info.part2_3_length = 0;
+        cod_info.big_values = 0;
+        cod_info.count1 = 0;
+        cod_info.global_gain = 210;
+        cod_info.scalefac_compress = 0;
+        /* mixed_block_flag, block_type was set in psymodel.c */
+        cod_info.table_select[0] = 0;
+        cod_info.table_select[1] = 0;
+        cod_info.table_select[2] = 0;
+        cod_info.subblock_gain[0] = 0;
+        cod_info.subblock_gain[1] = 0;
+        cod_info.subblock_gain[2] = 0;
+        cod_info.subblock_gain[3] = 0;
+        /* this one is always 0 */
+        cod_info.region0_count = 0;
+        cod_info.region1_count = 0;
+        cod_info.preflag = 0;
+        cod_info.scalefac_scale = 0;
+        cod_info.count1table_select = 0;
+        cod_info.part2_length = 0;
+        cod_info.sfb_lmax = Encoder.SBPSY_l;
+        cod_info.sfb_smin = Encoder.SBPSY_s;
+        cod_info.psy_lmax = gfc.sfb21_extra ? Encoder.SBMAX_l : Encoder.SBPSY_l;
+        cod_info.psymax = cod_info.psy_lmax;
+        cod_info.sfbmax = cod_info.sfb_lmax;
+        cod_info.sfbdivide = 11;
+        for (var sfb = 0; sfb < Encoder.SBMAX_l; sfb++) {
+            cod_info.width[sfb] = gfc.scalefac_band.l[sfb + 1]
+                - gfc.scalefac_band.l[sfb];
+            /* which is always 0. */
+            cod_info.window[sfb] = 3;
+        }
+        if (cod_info.block_type == Encoder.SHORT_TYPE) {
+            var ixwork = new_float(576);
+
+            cod_info.sfb_smin = 0;
+            cod_info.sfb_lmax = 0;
+            if (cod_info.mixed_block_flag != 0) {
+                abort();//fix cc 精简
+            }
+            cod_info.psymax = cod_info.sfb_lmax
+                + 3
+                * ((gfc.sfb21_extra ? Encoder.SBMAX_s : Encoder.SBPSY_s) - cod_info.sfb_smin);
+            cod_info.sfbmax = cod_info.sfb_lmax + 3
+                * (Encoder.SBPSY_s - cod_info.sfb_smin);
+            cod_info.sfbdivide = cod_info.sfbmax - 18;
+            cod_info.psy_lmax = cod_info.sfb_lmax;
+            /* re-order the short blocks, for more efficient encoding below */
+            /* By Takehiro TOMINAGA */
+            /*
+             * Within each scalefactor band, data is given for successive time
+             * windows, beginning with window 0 and ending with window 2. Within
+             * each window, the quantized values are then arranged in order of
+             * increasing frequency...
+             */
+            var ix = gfc.scalefac_band.l[cod_info.sfb_lmax];
+            System.arraycopy(cod_info.xr, 0, ixwork, 0, 576);
+            for (var sfb = cod_info.sfb_smin; sfb < Encoder.SBMAX_s; sfb++) {
+                var start = gfc.scalefac_band.s[sfb];
+                var end = gfc.scalefac_band.s[sfb + 1];
+                for (var window = 0; window < 3; window++) {
+                    for (var l = start; l < end; l++) {
+                        cod_info.xr[ix++] = ixwork[3 * l + window];
+                    }
+                }
+            }
+
+            var j = cod_info.sfb_lmax;
+            for (var sfb = cod_info.sfb_smin; sfb < Encoder.SBMAX_s; sfb++) {
+                cod_info.width[j] = cod_info.width[j + 1] = cod_info.width[j + 2] = gfc.scalefac_band.s[sfb + 1]
+                    - gfc.scalefac_band.s[sfb];
+                cod_info.window[j] = 0;
+                cod_info.window[j + 1] = 1;
+                cod_info.window[j + 2] = 2;
+                j += 3;
+            }
+        }
+
+        cod_info.count1bits = 0;
+        cod_info.sfb_partition_table = qupvt.nr_of_sfb_block[0][0];
+        cod_info.slen[0] = 0;
+        cod_info.slen[1] = 0;
+        cod_info.slen[2] = 0;
+        cod_info.slen[3] = 0;
+
+        cod_info.max_nonzero_coeff = 575;
+
+        /*
+         * fresh scalefactors are all zero
+         */
+        Arrays.fill(cod_info.scalefac, 0);
+
+        psfb21_analogsilence(gfc, cod_info);
+    };
+
+    function BinSearchDirection(ordinal) {
+        this.ordinal = ordinal;
+    }
+
+    BinSearchDirection.BINSEARCH_NONE = new BinSearchDirection(0);
+    BinSearchDirection.BINSEARCH_UP = new BinSearchDirection(1);
+    BinSearchDirection.BINSEARCH_DOWN = new BinSearchDirection(2);
+
+    /**
+     * author/date??
+     *
+     * binary step size search used by outer_loop to get a quantizer step size
+     * to start with
+     */
+    function bin_search_StepSize(gfc, cod_info, desired_rate, ch, xrpow) {
+        var nBits;
+        var CurrentStep = gfc.CurrentStep[ch];
+        var flagGoneOver = false;
+        var start = gfc.OldValue[ch];
+        var Direction = BinSearchDirection.BINSEARCH_NONE;
+        cod_info.global_gain = start;
+        desired_rate -= cod_info.part2_length;
+
+        for (; ;) {
+            var step;
+            nBits = tk.count_bits(gfc, xrpow, cod_info, null);
+
+            if (CurrentStep == 1 || nBits == desired_rate)
+                break;
+            /* nothing to adjust anymore */
+
+            if (nBits > desired_rate) {
+                /* increase Quantize_StepSize */
+                if (Direction == BinSearchDirection.BINSEARCH_DOWN)
+                    flagGoneOver = true;
+
+                if (flagGoneOver)
+                    CurrentStep /= 2;
+                Direction = BinSearchDirection.BINSEARCH_UP;
+                step = CurrentStep;
+            } else {
+                /* decrease Quantize_StepSize */
+                if (Direction == BinSearchDirection.BINSEARCH_UP)
+                    flagGoneOver = true;
+
+                if (flagGoneOver)
+                    CurrentStep /= 2;
+                Direction = BinSearchDirection.BINSEARCH_DOWN;
+                step = -CurrentStep;
+            }
+            cod_info.global_gain += step;
+            if (cod_info.global_gain < 0) {
+                abort();//fix cc 精简
+            }
+            if (cod_info.global_gain > 255) {
+                abort();//fix cc 精简
+            }
+        }
+
+
+        while (nBits > desired_rate && cod_info.global_gain < 255) {
+            cod_info.global_gain++;
+            nBits = tk.count_bits(gfc, xrpow, cod_info, null);
+        }
+        gfc.CurrentStep[ch] = (start - cod_info.global_gain >= 4) ? 4 : 2;
+        gfc.OldValue[ch] = cod_info.global_gain;
+        cod_info.part2_3_length = nBits;
+        return nBits;
+    }
+
+    //fix cc 精简
+
+    /**
+     * author/date??
+     *
+     * Function: Returns zero if there is a scalefac which has not been
+     * amplified. Otherwise it returns one.
+     */
+    function loop_break(cod_info) {
+        for (var sfb = 0; sfb < cod_info.sfbmax; sfb++)
+            if (cod_info.scalefac[sfb]
+                + cod_info.subblock_gain[cod_info.window[sfb]] == 0)
+                return false;
+
+        return true;
+    }
+
+    //fix cc 精简
+
+    function quant_compare(quant_comp, best, calc, gi, distort) {
+        /**
+         * noise is given in decibels (dB) relative to masking thesholds.<BR>
+         *
+         * over_noise: ??? (the previous comment is fully wrong)<BR>
+         * tot_noise: ??? (the previous comment is fully wrong)<BR>
+         * max_noise: max quantization noise
+         */
+        var better;
+
+        switch (quant_comp) {
+            default:
+            case 9:
+            {
+                if (best.over_count > 0) {
+                    /* there are distorted sfb */
+                    better = calc.over_SSD <= best.over_SSD;
+                    if (calc.over_SSD == best.over_SSD)
+                        better = calc.bits < best.bits;
+                } else {
+                    /* no distorted sfb */
+                    better = ((calc.max_noise < 0) && ((calc.max_noise * 10 + calc.bits) <= (best.max_noise * 10 + best.bits)));
+                }
+                break;
+            }
+
+            case 0:
+                better = calc.over_count < best.over_count
+                    || (calc.over_count == best.over_count && calc.over_noise < best.over_noise)
+                    || (calc.over_count == best.over_count
+                    && BitStream.EQ(calc.over_noise, best.over_noise) && calc.tot_noise < best.tot_noise);
+                break;
+
+            case 8:
+                abort();//fix cc 精简
+            //$FALL-THROUGH$
+            case 1:
+                better = calc.max_noise < best.max_noise;
+                break;
+            case 2:
+                better = calc.tot_noise < best.tot_noise;
+                break;
+            case 3:
+                better = (calc.tot_noise < best.tot_noise)
+                    && (calc.max_noise < best.max_noise);
+                break;
+            case 4:
+                better = (calc.max_noise <= 0.0 && best.max_noise > 0.2)
+                    || (calc.max_noise <= 0.0 && best.max_noise < 0.0
+                    && best.max_noise > calc.max_noise - 0.2 && calc.tot_noise < best.tot_noise)
+                    || (calc.max_noise <= 0.0 && best.max_noise > 0.0
+                    && best.max_noise > calc.max_noise - 0.2 && calc.tot_noise < best.tot_noise
+                    + best.over_noise)
+                    || (calc.max_noise > 0.0 && best.max_noise > -0.05
+                    && best.max_noise > calc.max_noise - 0.1 && calc.tot_noise
+                    + calc.over_noise < best.tot_noise
+                    + best.over_noise)
+                    || (calc.max_noise > 0.0 && best.max_noise > -0.1
+                    && best.max_noise > calc.max_noise - 0.15 && calc.tot_noise
+                    + calc.over_noise + calc.over_noise < best.tot_noise
+                    + best.over_noise + best.over_noise);
+                break;
+            case 5:
+                better = calc.over_noise < best.over_noise
+                    || (BitStream.EQ(calc.over_noise, best.over_noise) && calc.tot_noise < best.tot_noise);
+                break;
+            case 6:
+                better = calc.over_noise < best.over_noise
+                    || (BitStream.EQ(calc.over_noise, best.over_noise) && (calc.max_noise < best.max_noise || (BitStream
+                        .EQ(calc.max_noise, best.max_noise) && calc.tot_noise <= best.tot_noise)));
+                break;
+            case 7:
+                better = calc.over_count < best.over_count
+                    || calc.over_noise < best.over_noise;
+                break;
+        }
+
+        if (best.over_count == 0) {
+            /*
+             * If no distorted bands, only use this quantization if it is
+             * better, and if it uses less bits. Unfortunately, part2_3_length
+             * is sometimes a poor estimator of the final size at low bitrates.
+             */
+            better = better && calc.bits < best.bits;
+        }
+
+        return better;
+    }
+
+    /**
+     * author/date??
+     *
+     * <PRE>
+     *  Amplify the scalefactor bands that violate the masking threshold.
+     *  See ISO 11172-3 Section C.1.5.4.3.5
+     *
+     *  distort[] = noise/masking
+     *  distort[] > 1   ==> noise is not masked
+     *  distort[] < 1   ==> noise is masked
+     *  max_dist = maximum value of distort[]
+     *
+     *  Three algorithms:
+     *  noise_shaping_amp
+     *        0             Amplify all bands with distort[]>1.
+     *
+     *        1             Amplify all bands with distort[] >= max_dist^(.5);
+     *                     ( 50% in the db scale)
+     *
+     *        2             Amplify first band with distort[] >= max_dist;
+     *
+     *
+     *  For algorithms 0 and 1, if max_dist < 1, then amplify all bands
+     *  with distort[] >= .95*max_dist.  This is to make sure we always
+     *  amplify at least one band.
+     * </PRE>
+     */
+    function amp_scalefac_bands(gfp, cod_info, distort, xrpow, bRefine) {
+        var gfc = gfp.internal_flags;
+        var ifqstep34;
+
+        if (cod_info.scalefac_scale == 0) {
+            ifqstep34 = 1.29683955465100964055;
+            /* 2**(.75*.5) */
+        } else {
+            ifqstep34 = 1.68179283050742922612;
+            /* 2**(.75*1) */
+        }
+
+        /* compute maximum value of distort[] */
+        var trigger = 0;
+        for (var sfb = 0; sfb < cod_info.sfbmax; sfb++) {
+            if (trigger < distort[sfb])
+                trigger = distort[sfb];
+        }
+
+        var noise_shaping_amp = gfc.noise_shaping_amp;
+        if (noise_shaping_amp == 3) {
+            abort();//fix cc 精简
+        }
+        switch (noise_shaping_amp) {
+            case 2:
+                /* amplify exactly 1 band */
+                break;
+
+            case 1:
+                /* amplify bands within 50% of max (on db scale) */
+                if (trigger > 1.0)
+                    trigger = Math.pow(trigger, .5);
+                else
+                    trigger *= .95;
+                break;
+
+            case 0:
+            default:
+                /* ISO algorithm. amplify all bands with distort>1 */
+                if (trigger > 1.0)
+                    trigger = 1.0;
+                else
+                    trigger *= .95;
+                break;
+        }
+
+        var j = 0;
+        for (var sfb = 0; sfb < cod_info.sfbmax; sfb++) {
+            var width = cod_info.width[sfb];
+            var l;
+            j += width;
+            if (distort[sfb] < trigger)
+                continue;
+
+            if ((gfc.substep_shaping & 2) != 0) {
+                abort();//fix cc 精简
+            }
+            cod_info.scalefac[sfb]++;
+            for (l = -width; l < 0; l++) {
+                xrpow[j + l] *= ifqstep34;
+                if (xrpow[j + l] > cod_info.xrpow_max)
+                    cod_info.xrpow_max = xrpow[j + l];
+            }
+
+            if (gfc.noise_shaping_amp == 2)
+                return;
+        }
+    }
+
+    /**
+     * Takehiro Tominaga 2000-xx-xx
+     *
+     * turns on scalefac scale and adjusts scalefactors
+     */
+    function inc_scalefac_scale(cod_info, xrpow) {
+        var ifqstep34 = 1.29683955465100964055;
+
+        var j = 0;
+        for (var sfb = 0; sfb < cod_info.sfbmax; sfb++) {
+            var width = cod_info.width[sfb];
+            var s = cod_info.scalefac[sfb];
+            if (cod_info.preflag != 0)
+                s += qupvt.pretab[sfb];
+            j += width;
+            if ((s & 1) != 0) {
+                s++;
+                for (var l = -width; l < 0; l++) {
+                    xrpow[j + l] *= ifqstep34;
+                    if (xrpow[j + l] > cod_info.xrpow_max)
+                        cod_info.xrpow_max = xrpow[j + l];
+                }
+            }
+            cod_info.scalefac[sfb] = s >> 1;
+        }
+        cod_info.preflag = 0;
+        cod_info.scalefac_scale = 1;
+    }
+
+    /**
+     * Takehiro Tominaga 2000-xx-xx
+     *
+     * increases the subblock gain and adjusts scalefactors
+     */
+    function inc_subblock_gain(gfc, cod_info, xrpow) {
+        var sfb;
+        var scalefac = cod_info.scalefac;
+
+        /* subbloc_gain can't do anything in the long block region */
+        for (sfb = 0; sfb < cod_info.sfb_lmax; sfb++) {
+            if (scalefac[sfb] >= 16)
+                return true;
+        }
+
+        for (var window = 0; window < 3; window++) {
+            var s1 = 0;
+            var s2 = 0;
+
+            for (sfb = cod_info.sfb_lmax + window; sfb < cod_info.sfbdivide; sfb += 3) {
+                if (s1 < scalefac[sfb])
+                    s1 = scalefac[sfb];
+            }
+            for (; sfb < cod_info.sfbmax; sfb += 3) {
+                if (s2 < scalefac[sfb])
+                    s2 = scalefac[sfb];
+            }
+
+            if (s1 < 16 && s2 < 8)
+                continue;
+
+            if (cod_info.subblock_gain[window] >= 7)
+                return true;
+
+            /*
+             * even though there is no scalefactor for sfb12 subblock gain
+             * affects upper frequencies too, that's why we have to go up to
+             * SBMAX_s
+             */
+            cod_info.subblock_gain[window]++;
+            var j = gfc.scalefac_band.l[cod_info.sfb_lmax];
+            for (sfb = cod_info.sfb_lmax + window; sfb < cod_info.sfbmax; sfb += 3) {
+                var amp;
+                var width = cod_info.width[sfb];
+                var s = scalefac[sfb];
+                s = s - (4 >> cod_info.scalefac_scale);
+                if (s >= 0) {
+                    scalefac[sfb] = s;
+                    j += width * 3;
+                    continue;
+                }
+
+                scalefac[sfb] = 0;
+                {
+                    var gain = 210 + (s << (cod_info.scalefac_scale + 1));
+                    amp = qupvt.IPOW20(gain);
+                }
+                j += width * (window + 1);
+                for (var l = -width; l < 0; l++) {
+                    xrpow[j + l] *= amp;
+                    if (xrpow[j + l] > cod_info.xrpow_max)
+                        cod_info.xrpow_max = xrpow[j + l];
+                }
+                j += width * (3 - window - 1);
+            }
+
+            {
+                var amp = qupvt.IPOW20(202);
+                j += cod_info.width[sfb] * (window + 1);
+                for (var l = -cod_info.width[sfb]; l < 0; l++) {
+                    xrpow[j + l] *= amp;
+                    if (xrpow[j + l] > cod_info.xrpow_max)
+                        cod_info.xrpow_max = xrpow[j + l];
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * <PRE>
+     *  Takehiro Tominaga /date??
+     *  Robert Hegemann 2000-09-06: made a function of it
+     *
+     *  amplifies scalefactor bands,
+     *   - if all are already amplified returns 0
+     *   - if some bands are amplified too much:
+     *      * try to increase scalefac_scale
+     *      * if already scalefac_scale was set
+     *          try on short blocks to increase subblock gain
+     * </PRE>
+     */
+    function balance_noise(gfp, cod_info, distort, xrpow, bRefine) {
+        var gfc = gfp.internal_flags;
+
+        amp_scalefac_bands(gfp, cod_info, distort, xrpow, bRefine);
+
+        /*
+         * check to make sure we have not amplified too much loop_break returns
+         * 0 if there is an unamplified scalefac scale_bitcount returns 0 if no
+         * scalefactors are too large
+         */
+
+        var status = loop_break(cod_info);
+
+        if (status)
+            return false;
+        /* all bands amplified */
+
+        /*
+         * not all scalefactors have been amplified. so these scalefacs are
+         * possibly valid. encode them:
+         */
+        if (gfc.mode_gr == 2)
+            status = tk.scale_bitcount(cod_info);
+        else
+            status = tk.scale_bitcount_lsf(gfc, cod_info);
+
+        if (!status)
+            return true;
+        /* amplified some bands not exceeding limits */
+
+        /*
+         * some scalefactors are too large. lets try setting scalefac_scale=1
+         */
+        if (gfc.noise_shaping > 1) {
+            Arrays.fill(gfc.pseudohalf, 0);
+            if (0 == cod_info.scalefac_scale) {
+                inc_scalefac_scale(cod_info, xrpow);
+                status = false;
+            } else {
+                if (cod_info.block_type == Encoder.SHORT_TYPE
+                    && gfc.subblock_gain > 0) {
+                    status = (inc_subblock_gain(gfc, cod_info, xrpow) || loop_break(cod_info));
+                }
+            }
+        }
+
+        if (!status) {
+            if (gfc.mode_gr == 2)
+                status = tk.scale_bitcount(cod_info);
+            else
+                status = tk.scale_bitcount_lsf(gfc, cod_info);
+        }
+        return !status;
+    }
+
+    /**
+     * <PRE>
+     *  Function: The outer iteration loop controls the masking conditions
+     *  of all scalefactorbands. It computes the best scalefac and
+     *  global gain. This module calls the inner iteration loop
+     *
+     *  mt 5/99 completely rewritten to allow for bit reservoir control,
+     *  mid/side channels with L/R or mid/side masking thresholds,
+     *  and chooses best quantization instead of last quantization when
+     *  no distortion free quantization can be found.
+     *
+     *  added VBR support mt 5/99
+
+     *
+     *  some code shuffle rh 9/00
+     * </PRE>
+     *
+     * @param l3_xmin
+     *            allowed distortion
+     * @param xrpow
+     *            coloured magnitudes of spectral
+     * @param targ_bits
+     *            maximum allowed bits
+     */
+    this.outer_loop = function (gfp, cod_info, l3_xmin, xrpow, ch, targ_bits) {
+        var gfc = gfp.internal_flags;
+        var cod_info_w = new GrInfo();
+        var save_xrpow = new_float(576);
+        var distort = new_float(L3Side.SFBMAX);
+        var best_noise_info = new CalcNoiseResult();
+        var better;
+        var prev_noise = new CalcNoiseData();
+        var best_part2_3_length = 9999999;
+        var bEndOfSearch = false;
+        var bRefine = false;
+        var best_ggain_pass1 = 0;
+
+        bin_search_StepSize(gfc, cod_info, targ_bits, ch, xrpow);
+
+        if (0 == gfc.noise_shaping)
+        /* fast mode, no noise shaping, we are ready */
+            return 100;
+        /* default noise_info.over_count */
+
+        /* compute the distortion in this quantization */
+        /* coefficients and thresholds both l/r (or both mid/side) */
+        qupvt.calc_noise(cod_info, l3_xmin, distort, best_noise_info,
+            prev_noise);
+        best_noise_info.bits = cod_info.part2_3_length;
+
+        cod_info_w.assign(cod_info);
+        var age = 0;
+        System.arraycopy(xrpow, 0, save_xrpow, 0, 576);
+
+        while (!bEndOfSearch) {
+            /* BEGIN MAIN LOOP */
+            do {
+                var noise_info = new CalcNoiseResult();
+                var search_limit;
+                var maxggain = 255;
+
+                /*
+                 * When quantization with no distorted bands is found, allow up
+                 * to X new unsuccesful tries in serial. This gives us more
+                 * possibilities for different quant_compare modes. Much more
+                 * than 3 makes not a big difference, it is only slower.
+                 */
+
+                if ((gfc.substep_shaping & 2) != 0) {
+                    search_limit = 20;
+                } else {
+                    search_limit = 3;
+                }
+
+                /*
+                 * Check if the last scalefactor band is distorted. in VBR mode
+                 * we can't get rid of the distortion, so quit now and VBR mode
+                 * will try again with more bits. (makes a 10% speed increase,
+                 * the files I tested were binary identical, 2000/05/20 Robert
+                 * Hegemann) distort[] > 1 means noise > allowed noise
+                 */
+                if (gfc.sfb21_extra) {
+                    abort();//fix cc 精简
+                }
+
+                /* try a new scalefactor conbination on cod_info_w */
+                if (!balance_noise(gfp, cod_info_w, distort, xrpow, bRefine))
+                    break;
+                if (cod_info_w.scalefac_scale != 0)
+                    maxggain = 254;
+
+                /*
+                 * inner_loop starts with the initial quantization step computed
+                 * above and slowly increases until the bits < huff_bits. Thus
+                 * it is important not to start with too large of an inital
+                 * quantization step. Too small is ok, but inner_loop will take
+                 * longer
+                 */
+                var huff_bits = targ_bits - cod_info_w.part2_length;
+                if (huff_bits <= 0)
+                    break;
+
+                /*
+                 * increase quantizer stepsize until needed bits are below
+                 * maximum
+                 */
+                while ((cod_info_w.part2_3_length = tk.count_bits(gfc, xrpow,
+                    cod_info_w, prev_noise)) > huff_bits
+                && cod_info_w.global_gain <= maxggain)
+                    cod_info_w.global_gain++;
+
+                if (cod_info_w.global_gain > maxggain)
+                    break;
+
+                if (best_noise_info.over_count == 0) {
+
+                    while ((cod_info_w.part2_3_length = tk.count_bits(gfc,
+                        xrpow, cod_info_w, prev_noise)) > best_part2_3_length
+                    && cod_info_w.global_gain <= maxggain)
+                        cod_info_w.global_gain++;
+
+                    if (cod_info_w.global_gain > maxggain)
+                        break;
+                }
+
+                /* compute the distortion in this quantization */
+                qupvt.calc_noise(cod_info_w, l3_xmin, distort, noise_info,
+                    prev_noise);
+                noise_info.bits = cod_info_w.part2_3_length;
+
+                /*
+                 * check if this quantization is better than our saved
+                 * quantization
+                 */
+                if (cod_info.block_type != Encoder.SHORT_TYPE) {
+                    // NORM, START or STOP type
+                    better = gfp.quant_comp;
+                } else
+                    better = gfp.quant_comp_short;
+
+                better = quant_compare(better, best_noise_info, noise_info,
+                    cod_info_w, distort) ? 1 : 0;
+
+                /* save data so we can restore this quantization later */
+                if (better != 0) {
+                    best_part2_3_length = cod_info.part2_3_length;
+                    best_noise_info = noise_info;
+                    cod_info.assign(cod_info_w);
+                    age = 0;
+                    /* save data so we can restore this quantization later */
+                    /* store for later reuse */
+                    System.arraycopy(xrpow, 0, save_xrpow, 0, 576);
+                } else {
+                    /* early stop? */
+                    if (gfc.full_outer_loop == 0) {
+                        if (++age > search_limit
+                            && best_noise_info.over_count == 0)
+                            break;
+                        if ((gfc.noise_shaping_amp == 3) && bRefine && age > 30)
+                            break;
+                        if ((gfc.noise_shaping_amp == 3)
+                            && bRefine
+                            && (cod_info_w.global_gain - best_ggain_pass1) > 15)
+                            break;
+                    }
+                }
+            } while ((cod_info_w.global_gain + cod_info_w.scalefac_scale) < 255);
+
+            if (gfc.noise_shaping_amp == 3) {
+                abort();//fix cc 精简
+            } else {
+                bEndOfSearch = true;
+            }
+        }
+
+        /*
+         * finish up
+         */
+        if (gfp.VBR == VbrMode.vbr_rh || gfp.VBR == VbrMode.vbr_mtrh)
+        /* restore for reuse on next try */
+            System.arraycopy(save_xrpow, 0, xrpow, 0, 576);
+        /*
+         * do the 'substep shaping'
+         */
+        else if ((gfc.substep_shaping & 1) != 0)
+            abort();//fix cc 精简
+
+        return best_noise_info.over_count;
+    }
+
+    /**
+     * Robert Hegemann 2000-09-06
+     *
+     * update reservoir status after FINAL quantization/bitrate
+     */
+    this.iteration_finish_one = function (gfc, gr, ch) {
+        var l3_side = gfc.l3_side;
+        var cod_info = l3_side.tt[gr][ch];
+
+        /*
+         * try some better scalefac storage
+         */
+        tk.best_scalefac_store(gfc, gr, ch, l3_side);
+
+        /*
+         * best huffman_divide may save some bits too
+         */
+        if (gfc.use_best_huffman == 1)
+            tk.best_huffman_divide(gfc, cod_info);
+
+        /*
+         * update reservoir status after FINAL quantization/bitrate
+         */
+        rv.ResvAdjust(gfc, cod_info);
+    };
+
+    //fix cc 精简
+
+}
+
+/*
+ *      MP3 window subband -> subband filtering -> mdct routine
+ *
+ *      Copyright (c) 1999-2000 Takehiro Tominaga
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+/*
+ *         Special Thanks to Patrick De Smet for your advices.
+ */
+
+/* $Id: NewMDCT.java,v 1.11 2011/05/24 20:48:06 kenchis Exp $ */
+
+//package mp3;
+
+//import java.util.Arrays;
+
+
+
+function NewMDCT() {
+
+	var enwindow = [
+			-4.77e-07 * 0.740951125354959 / 2.384e-06,
+			1.03951e-04 * 0.740951125354959 / 2.384e-06,
+			9.53674e-04 * 0.740951125354959 / 2.384e-06,
+			2.841473e-03 * 0.740951125354959 / 2.384e-06,
+			3.5758972e-02 * 0.740951125354959 / 2.384e-06,
+			3.401756e-03 * 0.740951125354959 / 2.384e-06,
+			9.83715e-04 * 0.740951125354959 / 2.384e-06,
+			9.9182e-05 * 0.740951125354959 / 2.384e-06, /* 15 */
+			1.2398e-05 * 0.740951125354959 / 2.384e-06,
+			1.91212e-04 * 0.740951125354959 / 2.384e-06,
+			2.283096e-03 * 0.740951125354959 / 2.384e-06,
+			1.6994476e-02 * 0.740951125354959 / 2.384e-06,
+			-1.8756866e-02 * 0.740951125354959 / 2.384e-06,
+			-2.630711e-03 * 0.740951125354959 / 2.384e-06,
+			-2.47478e-04 * 0.740951125354959 / 2.384e-06,
+			-1.4782e-05 * 0.740951125354959 / 2.384e-06,
+			9.063471690191471e-01, 1.960342806591213e-01,
+
+			-4.77e-07 * 0.773010453362737 / 2.384e-06,
+			1.05858e-04 * 0.773010453362737 / 2.384e-06,
+			9.30786e-04 * 0.773010453362737 / 2.384e-06,
+			2.521515e-03 * 0.773010453362737 / 2.384e-06,
+			3.5694122e-02 * 0.773010453362737 / 2.384e-06,
+			3.643036e-03 * 0.773010453362737 / 2.384e-06,
+			9.91821e-04 * 0.773010453362737 / 2.384e-06,
+			9.6321e-05 * 0.773010453362737 / 2.384e-06, /* 14 */
+			1.1444e-05 * 0.773010453362737 / 2.384e-06,
+			1.65462e-04 * 0.773010453362737 / 2.384e-06,
+			2.110004e-03 * 0.773010453362737 / 2.384e-06,
+			1.6112804e-02 * 0.773010453362737 / 2.384e-06,
+			-1.9634247e-02 * 0.773010453362737 / 2.384e-06,
+			-2.803326e-03 * 0.773010453362737 / 2.384e-06,
+			-2.77042e-04 * 0.773010453362737 / 2.384e-06,
+			-1.6689e-05 * 0.773010453362737 / 2.384e-06,
+			8.206787908286602e-01, 3.901806440322567e-01,
+
+			-4.77e-07 * 0.803207531480645 / 2.384e-06,
+			1.07288e-04 * 0.803207531480645 / 2.384e-06,
+			9.02653e-04 * 0.803207531480645 / 2.384e-06,
+			2.174854e-03 * 0.803207531480645 / 2.384e-06,
+			3.5586357e-02 * 0.803207531480645 / 2.384e-06,
+			3.858566e-03 * 0.803207531480645 / 2.384e-06,
+			9.95159e-04 * 0.803207531480645 / 2.384e-06,
+			9.3460e-05 * 0.803207531480645 / 2.384e-06, /* 13 */
+			1.0014e-05 * 0.803207531480645 / 2.384e-06,
+			1.40190e-04 * 0.803207531480645 / 2.384e-06,
+			1.937389e-03 * 0.803207531480645 / 2.384e-06,
+			1.5233517e-02 * 0.803207531480645 / 2.384e-06,
+			-2.0506859e-02 * 0.803207531480645 / 2.384e-06,
+			-2.974033e-03 * 0.803207531480645 / 2.384e-06,
+			-3.07560e-04 * 0.803207531480645 / 2.384e-06,
+			-1.8120e-05 * 0.803207531480645 / 2.384e-06,
+			7.416505462720353e-01, 5.805693545089249e-01,
+
+			-4.77e-07 * 0.831469612302545 / 2.384e-06,
+			1.08242e-04 * 0.831469612302545 / 2.384e-06,
+			8.68797e-04 * 0.831469612302545 / 2.384e-06,
+			1.800537e-03 * 0.831469612302545 / 2.384e-06,
+			3.5435200e-02 * 0.831469612302545 / 2.384e-06,
+			4.049301e-03 * 0.831469612302545 / 2.384e-06,
+			9.94205e-04 * 0.831469612302545 / 2.384e-06,
+			9.0599e-05 * 0.831469612302545 / 2.384e-06, /* 12 */
+			9.060e-06 * 0.831469612302545 / 2.384e-06,
+			1.16348e-04 * 0.831469612302545 / 2.384e-06,
+			1.766682e-03 * 0.831469612302545 / 2.384e-06,
+			1.4358521e-02 * 0.831469612302545 / 2.384e-06,
+			-2.1372318e-02 * 0.831469612302545 / 2.384e-06,
+			-3.14188e-03 * 0.831469612302545 / 2.384e-06,
+			-3.39031e-04 * 0.831469612302545 / 2.384e-06,
+			-1.9550e-05 * 0.831469612302545 / 2.384e-06,
+			6.681786379192989e-01, 7.653668647301797e-01,
+
+			-4.77e-07 * 0.857728610000272 / 2.384e-06,
+			1.08719e-04 * 0.857728610000272 / 2.384e-06,
+			8.29220e-04 * 0.857728610000272 / 2.384e-06,
+			1.399517e-03 * 0.857728610000272 / 2.384e-06,
+			3.5242081e-02 * 0.857728610000272 / 2.384e-06,
+			4.215240e-03 * 0.857728610000272 / 2.384e-06,
+			9.89437e-04 * 0.857728610000272 / 2.384e-06,
+			8.7261e-05 * 0.857728610000272 / 2.384e-06, /* 11 */
+			8.106e-06 * 0.857728610000272 / 2.384e-06,
+			9.3937e-05 * 0.857728610000272 / 2.384e-06,
+			1.597881e-03 * 0.857728610000272 / 2.384e-06,
+			1.3489246e-02 * 0.857728610000272 / 2.384e-06,
+			-2.2228718e-02 * 0.857728610000272 / 2.384e-06,
+			-3.306866e-03 * 0.857728610000272 / 2.384e-06,
+			-3.71456e-04 * 0.857728610000272 / 2.384e-06,
+			-2.1458e-05 * 0.857728610000272 / 2.384e-06,
+			5.993769336819237e-01, 9.427934736519954e-01,
+
+			-4.77e-07 * 0.881921264348355 / 2.384e-06,
+			1.08719e-04 * 0.881921264348355 / 2.384e-06,
+			7.8392e-04 * 0.881921264348355 / 2.384e-06,
+			9.71317e-04 * 0.881921264348355 / 2.384e-06,
+			3.5007000e-02 * 0.881921264348355 / 2.384e-06,
+			4.357815e-03 * 0.881921264348355 / 2.384e-06,
+			9.80854e-04 * 0.881921264348355 / 2.384e-06,
+			8.3923e-05 * 0.881921264348355 / 2.384e-06, /* 10 */
+			7.629e-06 * 0.881921264348355 / 2.384e-06,
+			7.2956e-05 * 0.881921264348355 / 2.384e-06,
+			1.432419e-03 * 0.881921264348355 / 2.384e-06,
+			1.2627602e-02 * 0.881921264348355 / 2.384e-06,
+			-2.3074150e-02 * 0.881921264348355 / 2.384e-06,
+			-3.467083e-03 * 0.881921264348355 / 2.384e-06,
+			-4.04358e-04 * 0.881921264348355 / 2.384e-06,
+			-2.3365e-05 * 0.881921264348355 / 2.384e-06,
+			5.345111359507916e-01, 1.111140466039205e+00,
+
+			-9.54e-07 * 0.903989293123443 / 2.384e-06,
+			1.08242e-04 * 0.903989293123443 / 2.384e-06,
+			7.31945e-04 * 0.903989293123443 / 2.384e-06,
+			5.15938e-04 * 0.903989293123443 / 2.384e-06,
+			3.4730434e-02 * 0.903989293123443 / 2.384e-06,
+			4.477024e-03 * 0.903989293123443 / 2.384e-06,
+			9.68933e-04 * 0.903989293123443 / 2.384e-06,
+			8.0585e-05 * 0.903989293123443 / 2.384e-06, /* 9 */
+			6.676e-06 * 0.903989293123443 / 2.384e-06,
+			5.2929e-05 * 0.903989293123443 / 2.384e-06,
+			1.269817e-03 * 0.903989293123443 / 2.384e-06,
+			1.1775017e-02 * 0.903989293123443 / 2.384e-06,
+			-2.3907185e-02 * 0.903989293123443 / 2.384e-06,
+			-3.622532e-03 * 0.903989293123443 / 2.384e-06,
+			-4.38213e-04 * 0.903989293123443 / 2.384e-06,
+			-2.5272e-05 * 0.903989293123443 / 2.384e-06,
+			4.729647758913199e-01, 1.268786568327291e+00,
+
+			-9.54e-07 * 0.92387953251128675613 / 2.384e-06,
+			1.06812e-04 * 0.92387953251128675613 / 2.384e-06,
+			6.74248e-04 * 0.92387953251128675613 / 2.384e-06,
+			3.3379e-05 * 0.92387953251128675613 / 2.384e-06,
+			3.4412861e-02 * 0.92387953251128675613 / 2.384e-06,
+			4.573822e-03 * 0.92387953251128675613 / 2.384e-06,
+			9.54151e-04 * 0.92387953251128675613 / 2.384e-06,
+			7.6771e-05 * 0.92387953251128675613 / 2.384e-06,
+			6.199e-06 * 0.92387953251128675613 / 2.384e-06,
+			3.4332e-05 * 0.92387953251128675613 / 2.384e-06,
+			1.111031e-03 * 0.92387953251128675613 / 2.384e-06,
+			1.0933399e-02 * 0.92387953251128675613 / 2.384e-06,
+			-2.4725437e-02 * 0.92387953251128675613 / 2.384e-06,
+			-3.771782e-03 * 0.92387953251128675613 / 2.384e-06,
+			-4.72546e-04 * 0.92387953251128675613 / 2.384e-06,
+			-2.7657e-05 * 0.92387953251128675613 / 2.384e-06,
+			4.1421356237309504879e-01, /* tan(PI/8) */
+			1.414213562373095e+00,
+
+			-9.54e-07 * 0.941544065183021 / 2.384e-06,
+			1.05381e-04 * 0.941544065183021 / 2.384e-06,
+			6.10352e-04 * 0.941544065183021 / 2.384e-06,
+			-4.75883e-04 * 0.941544065183021 / 2.384e-06,
+			3.4055710e-02 * 0.941544065183021 / 2.384e-06,
+			4.649162e-03 * 0.941544065183021 / 2.384e-06,
+			9.35555e-04 * 0.941544065183021 / 2.384e-06,
+			7.3433e-05 * 0.941544065183021 / 2.384e-06, /* 7 */
+			5.245e-06 * 0.941544065183021 / 2.384e-06,
+			1.7166e-05 * 0.941544065183021 / 2.384e-06,
+			9.56535e-04 * 0.941544065183021 / 2.384e-06,
+			1.0103703e-02 * 0.941544065183021 / 2.384e-06,
+			-2.5527000e-02 * 0.941544065183021 / 2.384e-06,
+			-3.914356e-03 * 0.941544065183021 / 2.384e-06,
+			-5.07355e-04 * 0.941544065183021 / 2.384e-06,
+			-3.0041e-05 * 0.941544065183021 / 2.384e-06,
+			3.578057213145241e-01, 1.546020906725474e+00,
+
+			-9.54e-07 * 0.956940335732209 / 2.384e-06,
+			1.02520e-04 * 0.956940335732209 / 2.384e-06,
+			5.39303e-04 * 0.956940335732209 / 2.384e-06,
+			-1.011848e-03 * 0.956940335732209 / 2.384e-06,
+			3.3659935e-02 * 0.956940335732209 / 2.384e-06,
+			4.703045e-03 * 0.956940335732209 / 2.384e-06,
+			9.15051e-04 * 0.956940335732209 / 2.384e-06,
+			7.0095e-05 * 0.956940335732209 / 2.384e-06, /* 6 */
+			4.768e-06 * 0.956940335732209 / 2.384e-06,
+			9.54e-07 * 0.956940335732209 / 2.384e-06,
+			8.06808e-04 * 0.956940335732209 / 2.384e-06,
+			9.287834e-03 * 0.956940335732209 / 2.384e-06,
+			-2.6310921e-02 * 0.956940335732209 / 2.384e-06,
+			-4.048824e-03 * 0.956940335732209 / 2.384e-06,
+			-5.42164e-04 * 0.956940335732209 / 2.384e-06,
+			-3.2425e-05 * 0.956940335732209 / 2.384e-06,
+			3.033466836073424e-01, 1.662939224605090e+00,
+
+			-1.431e-06 * 0.970031253194544 / 2.384e-06,
+			9.9182e-05 * 0.970031253194544 / 2.384e-06,
+			4.62532e-04 * 0.970031253194544 / 2.384e-06,
+			-1.573563e-03 * 0.970031253194544 / 2.384e-06,
+			3.3225536e-02 * 0.970031253194544 / 2.384e-06,
+			4.737377e-03 * 0.970031253194544 / 2.384e-06,
+			8.91685e-04 * 0.970031253194544 / 2.384e-06,
+			6.6280e-05 * 0.970031253194544 / 2.384e-06, /* 5 */
+			4.292e-06 * 0.970031253194544 / 2.384e-06,
+			-1.3828e-05 * 0.970031253194544 / 2.384e-06,
+			6.61850e-04 * 0.970031253194544 / 2.384e-06,
+			8.487225e-03 * 0.970031253194544 / 2.384e-06,
+			-2.7073860e-02 * 0.970031253194544 / 2.384e-06,
+			-4.174709e-03 * 0.970031253194544 / 2.384e-06,
+			-5.76973e-04 * 0.970031253194544 / 2.384e-06,
+			-3.4809e-05 * 0.970031253194544 / 2.384e-06,
+			2.504869601913055e-01, 1.763842528696710e+00,
+
+			-1.431e-06 * 0.98078528040323 / 2.384e-06,
+			9.5367e-05 * 0.98078528040323 / 2.384e-06,
+			3.78609e-04 * 0.98078528040323 / 2.384e-06,
+			-2.161503e-03 * 0.98078528040323 / 2.384e-06,
+			3.2754898e-02 * 0.98078528040323 / 2.384e-06,
+			4.752159e-03 * 0.98078528040323 / 2.384e-06,
+			8.66413e-04 * 0.98078528040323 / 2.384e-06,
+			6.2943e-05 * 0.98078528040323 / 2.384e-06, /* 4 */
+			3.815e-06 * 0.98078528040323 / 2.384e-06,
+			-2.718e-05 * 0.98078528040323 / 2.384e-06,
+			5.22137e-04 * 0.98078528040323 / 2.384e-06,
+			7.703304e-03 * 0.98078528040323 / 2.384e-06,
+			-2.7815342e-02 * 0.98078528040323 / 2.384e-06,
+			-4.290581e-03 * 0.98078528040323 / 2.384e-06,
+			-6.11782e-04 * 0.98078528040323 / 2.384e-06,
+			-3.7670e-05 * 0.98078528040323 / 2.384e-06,
+			1.989123673796580e-01, 1.847759065022573e+00,
+
+			-1.907e-06 * 0.989176509964781 / 2.384e-06,
+			9.0122e-05 * 0.989176509964781 / 2.384e-06,
+			2.88486e-04 * 0.989176509964781 / 2.384e-06,
+			-2.774239e-03 * 0.989176509964781 / 2.384e-06,
+			3.2248020e-02 * 0.989176509964781 / 2.384e-06,
+			4.748821e-03 * 0.989176509964781 / 2.384e-06,
+			8.38757e-04 * 0.989176509964781 / 2.384e-06,
+			5.9605e-05 * 0.989176509964781 / 2.384e-06, /* 3 */
+			3.338e-06 * 0.989176509964781 / 2.384e-06,
+			-3.9577e-05 * 0.989176509964781 / 2.384e-06,
+			3.88145e-04 * 0.989176509964781 / 2.384e-06,
+			6.937027e-03 * 0.989176509964781 / 2.384e-06,
+			-2.8532982e-02 * 0.989176509964781 / 2.384e-06,
+			-4.395962e-03 * 0.989176509964781 / 2.384e-06,
+			-6.46591e-04 * 0.989176509964781 / 2.384e-06,
+			-4.0531e-05 * 0.989176509964781 / 2.384e-06,
+			1.483359875383474e-01, 1.913880671464418e+00,
+
+			-1.907e-06 * 0.995184726672197 / 2.384e-06,
+			8.4400e-05 * 0.995184726672197 / 2.384e-06,
+			1.91689e-04 * 0.995184726672197 / 2.384e-06,
+			-3.411293e-03 * 0.995184726672197 / 2.384e-06,
+			3.1706810e-02 * 0.995184726672197 / 2.384e-06,
+			4.728317e-03 * 0.995184726672197 / 2.384e-06,
+			8.09669e-04 * 0.995184726672197 / 2.384e-06,
+			5.579e-05 * 0.995184726672197 / 2.384e-06,
+			3.338e-06 * 0.995184726672197 / 2.384e-06,
+			-5.0545e-05 * 0.995184726672197 / 2.384e-06,
+			2.59876e-04 * 0.995184726672197 / 2.384e-06,
+			6.189346e-03 * 0.995184726672197 / 2.384e-06,
+			-2.9224873e-02 * 0.995184726672197 / 2.384e-06,
+			-4.489899e-03 * 0.995184726672197 / 2.384e-06,
+			-6.80923e-04 * 0.995184726672197 / 2.384e-06,
+			-4.3392e-05 * 0.995184726672197 / 2.384e-06,
+			9.849140335716425e-02, 1.961570560806461e+00,
+
+			-2.384e-06 * 0.998795456205172 / 2.384e-06,
+			7.7724e-05 * 0.998795456205172 / 2.384e-06,
+			8.8215e-05 * 0.998795456205172 / 2.384e-06,
+			-4.072189e-03 * 0.998795456205172 / 2.384e-06,
+			3.1132698e-02 * 0.998795456205172 / 2.384e-06,
+			4.691124e-03 * 0.998795456205172 / 2.384e-06,
+			7.79152e-04 * 0.998795456205172 / 2.384e-06,
+			5.2929e-05 * 0.998795456205172 / 2.384e-06,
+			2.861e-06 * 0.998795456205172 / 2.384e-06,
+			-6.0558e-05 * 0.998795456205172 / 2.384e-06,
+			1.37329e-04 * 0.998795456205172 / 2.384e-06,
+			5.462170e-03 * 0.998795456205172 / 2.384e-06,
+			-2.9890060e-02 * 0.998795456205172 / 2.384e-06,
+			-4.570484e-03 * 0.998795456205172 / 2.384e-06,
+			-7.14302e-04 * 0.998795456205172 / 2.384e-06,
+			-4.6253e-05 * 0.998795456205172 / 2.384e-06,
+			4.912684976946725e-02, 1.990369453344394e+00,
+
+			3.5780907e-02 * Util.SQRT2 * 0.5 / 2.384e-06,
+			1.7876148e-02 * Util.SQRT2 * 0.5 / 2.384e-06,
+			3.134727e-03 * Util.SQRT2 * 0.5 / 2.384e-06,
+			2.457142e-03 * Util.SQRT2 * 0.5 / 2.384e-06,
+			9.71317e-04 * Util.SQRT2 * 0.5 / 2.384e-06,
+			2.18868e-04 * Util.SQRT2 * 0.5 / 2.384e-06,
+			1.01566e-04 * Util.SQRT2 * 0.5 / 2.384e-06,
+			1.3828e-05 * Util.SQRT2 * 0.5 / 2.384e-06,
+
+			3.0526638e-02 / 2.384e-06, 4.638195e-03 / 2.384e-06,
+			7.47204e-04 / 2.384e-06, 4.9591e-05 / 2.384e-06,
+			4.756451e-03 / 2.384e-06, 2.1458e-05 / 2.384e-06,
+			-6.9618e-05 / 2.384e-06, /* 2.384e-06/2.384e-06 */
+	];
+
+	var NS = 12;
+	var NL = 36;
+
+	var win = [
+	    [
+	     2.382191739347913e-13,
+	     6.423305872147834e-13,
+	     9.400849094049688e-13,
+	     1.122435026096556e-12,
+	     1.183840321267481e-12,
+	     1.122435026096556e-12,
+	     9.400849094049690e-13,
+	     6.423305872147839e-13,
+	     2.382191739347918e-13,
+
+	     5.456116108943412e-12,
+	     4.878985199565852e-12,
+	     4.240448995017367e-12,
+	     3.559909094758252e-12,
+	     2.858043359288075e-12,
+	     2.156177623817898e-12,
+	     1.475637723558783e-12,
+	     8.371015190102974e-13,
+	     2.599706096327376e-13,
+
+	     -5.456116108943412e-12,
+	     -4.878985199565852e-12,
+	     -4.240448995017367e-12,
+	     -3.559909094758252e-12,
+	     -2.858043359288076e-12,
+	     -2.156177623817898e-12,
+	     -1.475637723558783e-12,
+	     -8.371015190102975e-13,
+	     -2.599706096327376e-13,
+
+	     -2.382191739347923e-13,
+	     -6.423305872147843e-13,
+	     -9.400849094049696e-13,
+	     -1.122435026096556e-12,
+	     -1.183840321267481e-12,
+	     -1.122435026096556e-12,
+	     -9.400849094049694e-13,
+	     -6.423305872147840e-13,
+	     -2.382191739347918e-13,
+	     ],
+	    [
+	     2.382191739347913e-13,
+	     6.423305872147834e-13,
+	     9.400849094049688e-13,
+	     1.122435026096556e-12,
+	     1.183840321267481e-12,
+	     1.122435026096556e-12,
+	     9.400849094049688e-13,
+	     6.423305872147841e-13,
+	     2.382191739347918e-13,
+
+	     5.456116108943413e-12,
+	     4.878985199565852e-12,
+	     4.240448995017367e-12,
+	     3.559909094758253e-12,
+	     2.858043359288075e-12,
+	     2.156177623817898e-12,
+	     1.475637723558782e-12,
+	     8.371015190102975e-13,
+	     2.599706096327376e-13,
+
+	     -5.461314069809755e-12,
+	     -4.921085770524055e-12,
+	     -4.343405037091838e-12,
+	     -3.732668368707687e-12,
+	     -3.093523840190885e-12,
+	     -2.430835727329465e-12,
+	     -1.734679010007751e-12,
+	     -9.748253656609281e-13,
+	     -2.797435120168326e-13,
+
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     -2.283748241799531e-13,
+	     -4.037858874020686e-13,
+	     -2.146547464825323e-13,
+	     ],
+	    [
+	     1.316524975873958e-01, /* win[SHORT_TYPE] */
+	     4.142135623730950e-01,
+	     7.673269879789602e-01,
+
+	     1.091308501069271e+00, /* tantab_l */
+	     1.303225372841206e+00,
+	     1.569685577117490e+00,
+	     1.920982126971166e+00,
+	     2.414213562373094e+00,
+	     3.171594802363212e+00,
+	     4.510708503662055e+00,
+	     7.595754112725146e+00,
+	     2.290376554843115e+01,
+
+	     0.98480775301220802032, /* cx */
+	     0.64278760968653936292,
+	     0.34202014332566882393,
+	     0.93969262078590842791,
+	     -0.17364817766693030343,
+	     -0.76604444311897790243,
+	     0.86602540378443870761,
+	     0.500000000000000e+00,
+
+	     -5.144957554275265e-01, /* ca */
+	     -4.717319685649723e-01,
+	     -3.133774542039019e-01,
+	     -1.819131996109812e-01,
+	     -9.457419252642064e-02,
+	     -4.096558288530405e-02,
+	     -1.419856857247115e-02,
+	     -3.699974673760037e-03,
+
+	     8.574929257125442e-01, /* cs */
+	     8.817419973177052e-01,
+	     9.496286491027329e-01,
+	     9.833145924917901e-01,
+	     9.955178160675857e-01,
+	     9.991605581781475e-01,
+	     9.998991952444470e-01,
+	     9.999931550702802e-01,
+	     ],
+	    [
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     0.000000000000000e+00,
+	     2.283748241799531e-13,
+	     4.037858874020686e-13,
+	     2.146547464825323e-13,
+
+	     5.461314069809755e-12,
+	     4.921085770524055e-12,
+	     4.343405037091838e-12,
+	     3.732668368707687e-12,
+	     3.093523840190885e-12,
+	     2.430835727329466e-12,
+	     1.734679010007751e-12,
+	     9.748253656609281e-13,
+	     2.797435120168326e-13,
+
+	     -5.456116108943413e-12,
+	     -4.878985199565852e-12,
+	     -4.240448995017367e-12,
+	     -3.559909094758253e-12,
+	     -2.858043359288075e-12,
+	     -2.156177623817898e-12,
+	     -1.475637723558782e-12,
+	     -8.371015190102975e-13,
+	     -2.599706096327376e-13,
+
+	     -2.382191739347913e-13,
+	     -6.423305872147834e-13,
+	     -9.400849094049688e-13,
+	     -1.122435026096556e-12,
+	     -1.183840321267481e-12,
+	     -1.122435026096556e-12,
+	     -9.400849094049688e-13,
+	     -6.423305872147841e-13,
+	     -2.382191739347918e-13,
+	     ]
+	];
+
+	var tantab_l = win[Encoder.SHORT_TYPE];
+	var cx = win[Encoder.SHORT_TYPE];
+	var ca = win[Encoder.SHORT_TYPE];
+	var cs = win[Encoder.SHORT_TYPE];
+
+	/**
+	 * new IDCT routine written by Takehiro TOMINAGA
+	 *
+	 * PURPOSE: Overlapping window on PCM samples<BR>
+	 *
+	 * SEMANTICS:<BR>
+	 * 32 16-bit pcm samples are scaled to fractional 2's complement and
+	 * concatenated to the end of the window buffer #x#. The updated window
+	 * buffer #x# is then windowed by the analysis window #c# to produce the
+	 * windowed sample #z#
+	 */
+	var order = [
+	    0, 1, 16, 17, 8, 9, 24, 25, 4, 5, 20, 21, 12, 13, 28, 29,
+	    2, 3, 18, 19, 10, 11, 26, 27, 6, 7, 22, 23, 14, 15, 30, 31
+	];
+
+	/**
+	 * returns sum_j=0^31 a[j]*cos(PI*j*(k+1/2)/32), 0<=k<32
+	 */
+	function window_subband(x1, x1Pos, a) {
+		var wp = 10;
+
+		var x2 = x1Pos + 238 - 14 - 286;
+
+		for (var i = -15; i < 0; i++) {
+			var w, s, t;
+
+			w = enwindow[wp + -10];
+			s = x1[x2 + -224] * w;
+			t = x1[x1Pos + 224] * w;
+			w = enwindow[wp + -9];
+			s += x1[x2 + -160] * w;
+			t += x1[x1Pos + 160] * w;
+			w = enwindow[wp + -8];
+			s += x1[x2 + -96] * w;
+			t += x1[x1Pos + 96] * w;
+			w = enwindow[wp + -7];
+			s += x1[x2 + -32] * w;
+			t += x1[x1Pos + 32] * w;
+			w = enwindow[wp + -6];
+			s += x1[x2 + 32] * w;
+			t += x1[x1Pos + -32] * w;
+			w = enwindow[wp + -5];
+			s += x1[x2 + 96] * w;
+			t += x1[x1Pos + -96] * w;
+			w = enwindow[wp + -4];
+			s += x1[x2 + 160] * w;
+			t += x1[x1Pos + -160] * w;
+			w = enwindow[wp + -3];
+			s += x1[x2 + 224] * w;
+			t += x1[x1Pos + -224] * w;
+
+			w = enwindow[wp + -2];
+			s += x1[x1Pos + -256] * w;
+			t -= x1[x2 + 256] * w;
+			w = enwindow[wp + -1];
+			s += x1[x1Pos + -192] * w;
+			t -= x1[x2 + 192] * w;
+			w = enwindow[wp + 0];
+			s += x1[x1Pos + -128] * w;
+			t -= x1[x2 + 128] * w;
+			w = enwindow[wp + 1];
+			s += x1[x1Pos + -64] * w;
+			t -= x1[x2 + 64] * w;
+			w = enwindow[wp + 2];
+			s += x1[x1Pos + 0] * w;
+			t -= x1[x2 + 0] * w;
+			w = enwindow[wp + 3];
+			s += x1[x1Pos + 64] * w;
+			t -= x1[x2 + -64] * w;
+			w = enwindow[wp + 4];
+			s += x1[x1Pos + 128] * w;
+			t -= x1[x2 + -128] * w;
+			w = enwindow[wp + 5];
+			s += x1[x1Pos + 192] * w;
+			t -= x1[x2 + -192] * w;
+
+			/*
+			 * this multiplyer could be removed, but it needs more 256 FLOAT
+			 * data. thinking about the data cache performance, I think we
+			 * should not use such a huge table. tt 2000/Oct/25
+			 */
+			s *= enwindow[wp + 6];
+			w = t - s;
+			a[30 + i * 2] = t + s;
+			a[31 + i * 2] = enwindow[wp + 7] * w;
+			wp += 18;
+			x1Pos--;
+			x2++;
+		}
+		{
+			var s, t, u, v;
+			t = x1[x1Pos + -16] * enwindow[wp + -10];
+			s = x1[x1Pos + -32] * enwindow[wp + -2];
+			t += (x1[x1Pos + -48] - x1[x1Pos + 16]) * enwindow[wp + -9];
+			s += x1[x1Pos + -96] * enwindow[wp + -1];
+			t += (x1[x1Pos + -80] + x1[x1Pos + 48]) * enwindow[wp + -8];
+			s += x1[x1Pos + -160] * enwindow[wp + 0];
+			t += (x1[x1Pos + -112] - x1[x1Pos + 80]) * enwindow[wp + -7];
+			s += x1[x1Pos + -224] * enwindow[wp + 1];
+			t += (x1[x1Pos + -144] + x1[x1Pos + 112]) * enwindow[wp + -6];
+			s -= x1[x1Pos + 32] * enwindow[wp + 2];
+			t += (x1[x1Pos + -176] - x1[x1Pos + 144]) * enwindow[wp + -5];
+			s -= x1[x1Pos + 96] * enwindow[wp + 3];
+			t += (x1[x1Pos + -208] + x1[x1Pos + 176]) * enwindow[wp + -4];
+			s -= x1[x1Pos + 160] * enwindow[wp + 4];
+			t += (x1[x1Pos + -240] - x1[x1Pos + 208]) * enwindow[wp + -3];
+			s -= x1[x1Pos + 224];
+
+			u = s - t;
+			v = s + t;
+
+			t = a[14];
+			s = a[15] - t;
+
+			a[31] = v + t; /* A0 */
+			a[30] = u + s; /* A1 */
+			a[15] = u - s; /* A2 */
+			a[14] = v - t; /* A3 */
+		}
+		{
+			var xr;
+			xr = a[28] - a[0];
+			a[0] += a[28];
+			a[28] = xr * enwindow[wp + -2 * 18 + 7];
+			xr = a[29] - a[1];
+			a[1] += a[29];
+			a[29] = xr * enwindow[wp + -2 * 18 + 7];
+
+			xr = a[26] - a[2];
+			a[2] += a[26];
+			a[26] = xr * enwindow[wp + -4 * 18 + 7];
+			xr = a[27] - a[3];
+			a[3] += a[27];
+			a[27] = xr * enwindow[wp + -4 * 18 + 7];
+
+			xr = a[24] - a[4];
+			a[4] += a[24];
+			a[24] = xr * enwindow[wp + -6 * 18 + 7];
+			xr = a[25] - a[5];
+			a[5] += a[25];
+			a[25] = xr * enwindow[wp + -6 * 18 + 7];
+
+			xr = a[22] - a[6];
+			a[6] += a[22];
+			a[22] = xr * Util.SQRT2;
+			xr = a[23] - a[7];
+			a[7] += a[23];
+			a[23] = xr * Util.SQRT2 - a[7];
+			a[7] -= a[6];
+			a[22] -= a[7];
+			a[23] -= a[22];
+
+			xr = a[6];
+			a[6] = a[31] - xr;
+			a[31] = a[31] + xr;
+			xr = a[7];
+			a[7] = a[30] - xr;
+			a[30] = a[30] + xr;
+			xr = a[22];
+			a[22] = a[15] - xr;
+			a[15] = a[15] + xr;
+			xr = a[23];
+			a[23] = a[14] - xr;
+			a[14] = a[14] + xr;
+
+			xr = a[20] - a[8];
+			a[8] += a[20];
+			a[20] = xr * enwindow[wp + -10 * 18 + 7];
+			xr = a[21] - a[9];
+			a[9] += a[21];
+			a[21] = xr * enwindow[wp + -10 * 18 + 7];
+
+			xr = a[18] - a[10];
+			a[10] += a[18];
+			a[18] = xr * enwindow[wp + -12 * 18 + 7];
+			xr = a[19] - a[11];
+			a[11] += a[19];
+			a[19] = xr * enwindow[wp + -12 * 18 + 7];
+
+			xr = a[16] - a[12];
+			a[12] += a[16];
+			a[16] = xr * enwindow[wp + -14 * 18 + 7];
+			xr = a[17] - a[13];
+			a[13] += a[17];
+			a[17] = xr * enwindow[wp + -14 * 18 + 7];
+
+			xr = -a[20] + a[24];
+			a[20] += a[24];
+			a[24] = xr * enwindow[wp + -12 * 18 + 7];
+			xr = -a[21] + a[25];
+			a[21] += a[25];
+			a[25] = xr * enwindow[wp + -12 * 18 + 7];
+
+			xr = a[4] - a[8];
+			a[4] += a[8];
+			a[8] = xr * enwindow[wp + -12 * 18 + 7];
+			xr = a[5] - a[9];
+			a[5] += a[9];
+			a[9] = xr * enwindow[wp + -12 * 18 + 7];
+
+			xr = a[0] - a[12];
+			a[0] += a[12];
+			a[12] = xr * enwindow[wp + -4 * 18 + 7];
+			xr = a[1] - a[13];
+			a[1] += a[13];
+			a[13] = xr * enwindow[wp + -4 * 18 + 7];
+			xr = a[16] - a[28];
+			a[16] += a[28];
+			a[28] = xr * enwindow[wp + -4 * 18 + 7];
+			xr = -a[17] + a[29];
+			a[17] += a[29];
+			a[29] = xr * enwindow[wp + -4 * 18 + 7];
+
+			xr = Util.SQRT2 * (a[2] - a[10]);
+			a[2] += a[10];
+			a[10] = xr;
+			xr = Util.SQRT2 * (a[3] - a[11]);
+			a[3] += a[11];
+			a[11] = xr;
+			xr = Util.SQRT2 * (-a[18] + a[26]);
+			a[18] += a[26];
+			a[26] = xr - a[18];
+			xr = Util.SQRT2 * (-a[19] + a[27]);
+			a[19] += a[27];
+			a[27] = xr - a[19];
+
+			xr = a[2];
+			a[19] -= a[3];
+			a[3] -= xr;
+			a[2] = a[31] - xr;
+			a[31] += xr;
+			xr = a[3];
+			a[11] -= a[19];
+			a[18] -= xr;
+			a[3] = a[30] - xr;
+			a[30] += xr;
+			xr = a[18];
+			a[27] -= a[11];
+			a[19] -= xr;
+			a[18] = a[15] - xr;
+			a[15] += xr;
+
+			xr = a[19];
+			a[10] -= xr;
+			a[19] = a[14] - xr;
+			a[14] += xr;
+			xr = a[10];
+			a[11] -= xr;
+			a[10] = a[23] - xr;
+			a[23] += xr;
+			xr = a[11];
+			a[26] -= xr;
+			a[11] = a[22] - xr;
+			a[22] += xr;
+			xr = a[26];
+			a[27] -= xr;
+			a[26] = a[7] - xr;
+			a[7] += xr;
+
+			xr = a[27];
+			a[27] = a[6] - xr;
+			a[6] += xr;
+
+			xr = Util.SQRT2 * (a[0] - a[4]);
+			a[0] += a[4];
+			a[4] = xr;
+			xr = Util.SQRT2 * (a[1] - a[5]);
+			a[1] += a[5];
+			a[5] = xr;
+			xr = Util.SQRT2 * (a[16] - a[20]);
+			a[16] += a[20];
+			a[20] = xr;
+			xr = Util.SQRT2 * (a[17] - a[21]);
+			a[17] += a[21];
+			a[21] = xr;
+
+			xr = -Util.SQRT2 * (a[8] - a[12]);
+			a[8] += a[12];
+			a[12] = xr - a[8];
+			xr = -Util.SQRT2 * (a[9] - a[13]);
+			a[9] += a[13];
+			a[13] = xr - a[9];
+			xr = -Util.SQRT2 * (a[25] - a[29]);
+			a[25] += a[29];
+			a[29] = xr - a[25];
+			xr = -Util.SQRT2 * (a[24] + a[28]);
+			a[24] -= a[28];
+			a[28] = xr - a[24];
+
+			xr = a[24] - a[16];
+			a[24] = xr;
+			xr = a[20] - xr;
+			a[20] = xr;
+			xr = a[28] - xr;
+			a[28] = xr;
+
+			xr = a[25] - a[17];
+			a[25] = xr;
+			xr = a[21] - xr;
+			a[21] = xr;
+			xr = a[29] - xr;
+			a[29] = xr;
+
+			xr = a[17] - a[1];
+			a[17] = xr;
+			xr = a[9] - xr;
+			a[9] = xr;
+			xr = a[25] - xr;
+			a[25] = xr;
+			xr = a[5] - xr;
+			a[5] = xr;
+			xr = a[21] - xr;
+			a[21] = xr;
+			xr = a[13] - xr;
+			a[13] = xr;
+			xr = a[29] - xr;
+			a[29] = xr;
+
+			xr = a[1] - a[0];
+			a[1] = xr;
+			xr = a[16] - xr;
+			a[16] = xr;
+			xr = a[17] - xr;
+			a[17] = xr;
+			xr = a[8] - xr;
+			a[8] = xr;
+			xr = a[9] - xr;
+			a[9] = xr;
+			xr = a[24] - xr;
+			a[24] = xr;
+			xr = a[25] - xr;
+			a[25] = xr;
+			xr = a[4] - xr;
+			a[4] = xr;
+			xr = a[5] - xr;
+			a[5] = xr;
+			xr = a[20] - xr;
+			a[20] = xr;
+			xr = a[21] - xr;
+			a[21] = xr;
+			xr = a[12] - xr;
+			a[12] = xr;
+			xr = a[13] - xr;
+			a[13] = xr;
+			xr = a[28] - xr;
+			a[28] = xr;
+			xr = a[29] - xr;
+			a[29] = xr;
+
+			xr = a[0];
+			a[0] += a[31];
+			a[31] -= xr;
+			xr = a[1];
+			a[1] += a[30];
+			a[30] -= xr;
+			xr = a[16];
+			a[16] += a[15];
+			a[15] -= xr;
+			xr = a[17];
+			a[17] += a[14];
+			a[14] -= xr;
+			xr = a[8];
+			a[8] += a[23];
+			a[23] -= xr;
+			xr = a[9];
+			a[9] += a[22];
+			a[22] -= xr;
+			xr = a[24];
+			a[24] += a[7];
+			a[7] -= xr;
+			xr = a[25];
+			a[25] += a[6];
+			a[6] -= xr;
+			xr = a[4];
+			a[4] += a[27];
+			a[27] -= xr;
+			xr = a[5];
+			a[5] += a[26];
+			a[26] -= xr;
+			xr = a[20];
+			a[20] += a[11];
+			a[11] -= xr;
+			xr = a[21];
+			a[21] += a[10];
+			a[10] -= xr;
+			xr = a[12];
+			a[12] += a[19];
+			a[19] -= xr;
+			xr = a[13];
+			a[13] += a[18];
+			a[18] -= xr;
+			xr = a[28];
+			a[28] += a[3];
+			a[3] -= xr;
+			xr = a[29];
+			a[29] += a[2];
+			a[2] -= xr;
+		}
+	}
+
+	/**
+	 * Function: Calculation of the MDCT In the case of long blocks (type 0,1,3)
+	 * there are 36 coefficents in the time domain and 18 in the frequency
+	 * domain.<BR>
+	 * In the case of short blocks (type 2) there are 3 transformations with
+	 * short length. This leads to 12 coefficents in the time and 6 in the
+	 * frequency domain. In this case the results are stored side by side in the
+	 * vector out[].
+	 *
+	 * New layer3
+	 */
+	function mdct_short(inout, inoutPos) {
+		for (var l = 0; l < 3; l++) {
+			var tc0, tc1, tc2, ts0, ts1, ts2;
+
+			ts0 = inout[inoutPos + 2 * 3] * win[Encoder.SHORT_TYPE][0]
+					- inout[inoutPos + 5 * 3];
+			tc0 = inout[inoutPos + 0 * 3] * win[Encoder.SHORT_TYPE][2]
+					- inout[inoutPos + 3 * 3];
+			tc1 = ts0 + tc0;
+			tc2 = ts0 - tc0;
+
+			ts0 = inout[inoutPos + 5 * 3] * win[Encoder.SHORT_TYPE][0]
+					+ inout[inoutPos + 2 * 3];
+			tc0 = inout[inoutPos + 3 * 3] * win[Encoder.SHORT_TYPE][2]
+					+ inout[inoutPos + 0 * 3];
+			ts1 = ts0 + tc0;
+			ts2 = -ts0 + tc0;
+
+			tc0 = (inout[inoutPos + 1 * 3] * win[Encoder.SHORT_TYPE][1] - inout[inoutPos + 4 * 3]) * 2.069978111953089e-11;
+			/*
+			 * tritab_s [ 1 ]
+			 */
+			ts0 = (inout[inoutPos + 4 * 3] * win[Encoder.SHORT_TYPE][1] + inout[inoutPos + 1 * 3]) * 2.069978111953089e-11;
+			/*
+			 * tritab_s [ 1 ]
+			 */
+			inout[inoutPos + 3 * 0] = tc1 * 1.907525191737280e-11 + tc0;
+			/*
+			 * tritab_s[ 2 ]
+			 */
+			inout[inoutPos + 3 * 5] = -ts1 * 1.907525191737280e-11 + ts0;
+			/*
+			 * tritab_s[0 ]
+			 */
+			tc2 = tc2 * 0.86602540378443870761 * 1.907525191737281e-11;
+			/*
+			 * tritab_s[ 2]
+			 */
+			ts1 = ts1 * 0.5 * 1.907525191737281e-11 + ts0;
+			inout[inoutPos + 3 * 1] = tc2 - ts1;
+			inout[inoutPos + 3 * 2] = tc2 + ts1;
+
+			tc1 = tc1 * 0.5 * 1.907525191737281e-11 - tc0;
+			ts2 = ts2 * 0.86602540378443870761 * 1.907525191737281e-11;
+			/*
+			 * tritab_s[ 0]
+			 */
+			inout[inoutPos + 3 * 3] = tc1 + ts2;
+			inout[inoutPos + 3 * 4] = tc1 - ts2;
+
+			inoutPos++;
+		}
+	}
+
+	function mdct_long(out, outPos, _in) {
+		var ct, st;
+		{
+			var tc1, tc2, tc3, tc4, ts5, ts6, ts7, ts8;
+			/* 1,2, 5,6, 9,10, 13,14, 17 */
+			tc1 = _in[17] - _in[9];
+			tc3 = _in[15] - _in[11];
+			tc4 = _in[14] - _in[12];
+			ts5 = _in[0] + _in[8];
+			ts6 = _in[1] + _in[7];
+			ts7 = _in[2] + _in[6];
+			ts8 = _in[3] + _in[5];
+
+			out[outPos + 17] = (ts5 + ts7 - ts8) - (ts6 - _in[4]);
+			st = (ts5 + ts7 - ts8) * cx[12 + 7] + (ts6 - _in[4]);
+			ct = (tc1 - tc3 - tc4) * cx[12 + 6];
+			out[outPos + 5] = ct + st;
+			out[outPos + 6] = ct - st;
+
+			tc2 = (_in[16] - _in[10]) * cx[12 + 6];
+			ts6 = ts6 * cx[12 + 7] + _in[4];
+			ct = tc1 * cx[12 + 0] + tc2 + tc3 * cx[12 + 1] + tc4 * cx[12 + 2];
+			st = -ts5 * cx[12 + 4] + ts6 - ts7 * cx[12 + 5] + ts8 * cx[12 + 3];
+			out[outPos + 1] = ct + st;
+			out[outPos + 2] = ct - st;
+
+			ct = tc1 * cx[12 + 1] - tc2 - tc3 * cx[12 + 2] + tc4 * cx[12 + 0];
+			st = -ts5 * cx[12 + 5] + ts6 - ts7 * cx[12 + 3] + ts8 * cx[12 + 4];
+			out[outPos + 9] = ct + st;
+			out[outPos + 10] = ct - st;
+
+			ct = tc1 * cx[12 + 2] - tc2 + tc3 * cx[12 + 0] - tc4 * cx[12 + 1];
+			st = ts5 * cx[12 + 3] - ts6 + ts7 * cx[12 + 4] - ts8 * cx[12 + 5];
+			out[outPos + 13] = ct + st;
+			out[outPos + 14] = ct - st;
+		}
+		{
+			var ts1, ts2, ts3, ts4, tc5, tc6, tc7, tc8;
+
+			ts1 = _in[8] - _in[0];
+			ts3 = _in[6] - _in[2];
+			ts4 = _in[5] - _in[3];
+			tc5 = _in[17] + _in[9];
+			tc6 = _in[16] + _in[10];
+			tc7 = _in[15] + _in[11];
+			tc8 = _in[14] + _in[12];
+
+			out[outPos + 0] = (tc5 + tc7 + tc8) + (tc6 + _in[13]);
+			ct = (tc5 + tc7 + tc8) * cx[12 + 7] - (tc6 + _in[13]);
+			st = (ts1 - ts3 + ts4) * cx[12 + 6];
+			out[outPos + 11] = ct + st;
+			out[outPos + 12] = ct - st;
+
+			ts2 = (_in[7] - _in[1]) * cx[12 + 6];
+			tc6 = _in[13] - tc6 * cx[12 + 7];
+			ct = tc5 * cx[12 + 3] - tc6 + tc7 * cx[12 + 4] + tc8 * cx[12 + 5];
+			st = ts1 * cx[12 + 2] + ts2 + ts3 * cx[12 + 0] + ts4 * cx[12 + 1];
+			out[outPos + 3] = ct + st;
+			out[outPos + 4] = ct - st;
+
+			ct = -tc5 * cx[12 + 5] + tc6 - tc7 * cx[12 + 3] - tc8 * cx[12 + 4];
+			st = ts1 * cx[12 + 1] + ts2 - ts3 * cx[12 + 2] - ts4 * cx[12 + 0];
+			out[outPos + 7] = ct + st;
+			out[outPos + 8] = ct - st;
+
+			ct = -tc5 * cx[12 + 4] + tc6 - tc7 * cx[12 + 5] - tc8 * cx[12 + 3];
+			st = ts1 * cx[12 + 0] - ts2 + ts3 * cx[12 + 1] - ts4 * cx[12 + 2];
+			out[outPos + 15] = ct + st;
+			out[outPos + 16] = ct - st;
+		}
+	}
+
+	this.mdct_sub48 = function(gfc, w0, w1) {
+		var wk = w0;
+		var wkPos = 286;
+		/* thinking cache performance, ch->gr loop is better than gr->ch loop */
+		for (var ch = 0; ch < gfc.channels_out; ch++) {
+			for (var gr = 0; gr < gfc.mode_gr; gr++) {
+				var band;
+				var gi = (gfc.l3_side.tt[gr][ch]);
+				var mdct_enc = gi.xr;
+				var mdct_encPos = 0;
+				var samp = gfc.sb_sample[ch][1 - gr];
+				var sampPos = 0;
+
+				for (var k = 0; k < 18 / 2; k++) {
+					window_subband(wk, wkPos, samp[sampPos]);
+					window_subband(wk, wkPos + 32, samp[sampPos + 1]);
+					sampPos += 2;
+					wkPos += 64;
+					/*
+					 * Compensate for inversion in the analysis filter
+					 */
+					for (band = 1; band < 32; band += 2) {
+						samp[sampPos - 1][band] *= -1;
+					}
+				}
+
+				/*
+				 * Perform imdct of 18 previous subband samples + 18 current
+				 * subband samples
+				 */
+				for (band = 0; band < 32; band++, mdct_encPos += 18) {
+					var type = gi.block_type;
+					var band0 = gfc.sb_sample[ch][gr];
+					var band1 = gfc.sb_sample[ch][1 - gr];
+					if (gi.mixed_block_flag != 0 && band < 2)
+						type = 0;
+					if (gfc.amp_filter[band] < 1e-12) {
+						Arrays.fill(mdct_enc, mdct_encPos + 0,
+								mdct_encPos + 18, 0);
+					} else {
+						if (gfc.amp_filter[band] < 1.0) {
+							abort();//fix cc 精简
+						}
+						if (type == Encoder.SHORT_TYPE) {
+							for (var k = -NS / 4; k < 0; k++) {
+								var w = win[Encoder.SHORT_TYPE][k + 3];
+								mdct_enc[mdct_encPos + k * 3 + 9] = band0[9 + k][order[band]]
+										* w - band0[8 - k][order[band]];
+								mdct_enc[mdct_encPos + k * 3 + 18] = band0[14 - k][order[band]]
+										* w + band0[15 + k][order[band]];
+								mdct_enc[mdct_encPos + k * 3 + 10] = band0[15 + k][order[band]]
+										* w - band0[14 - k][order[band]];
+								mdct_enc[mdct_encPos + k * 3 + 19] = band1[2 - k][order[band]]
+										* w + band1[3 + k][order[band]];
+								mdct_enc[mdct_encPos + k * 3 + 11] = band1[3 + k][order[band]]
+										* w - band1[2 - k][order[band]];
+								mdct_enc[mdct_encPos + k * 3 + 20] = band1[8 - k][order[band]]
+										* w + band1[9 + k][order[band]];
+							}
+							mdct_short(mdct_enc, mdct_encPos);
+						} else {
+							var work = new_float(18);
+							for (var k = -NL / 4; k < 0; k++) {
+								var a, b;
+								a = win[type][k + 27]
+										* band1[k + 9][order[band]]
+										+ win[type][k + 36]
+										* band1[8 - k][order[band]];
+								b = win[type][k + 9]
+										* band0[k + 9][order[band]]
+										- win[type][k + 18]
+										* band0[8 - k][order[band]];
+								work[k + 9] = a - b * tantab_l[3 + k + 9];
+								work[k + 18] = a * tantab_l[3 + k + 9] + b;
+							}
+
+							mdct_long(mdct_enc, mdct_encPos, work);
+						}
+					}
+					/*
+					 * Perform aliasing reduction butterfly
+					 */
+					if (type != Encoder.SHORT_TYPE && band != 0) {
+						for (var k = 7; k >= 0; --k) {
+							var bu, bd;
+							bu = mdct_enc[mdct_encPos + k] * ca[20 + k]
+									+ mdct_enc[mdct_encPos + -1 - k]
+									* cs[28 + k];
+							bd = mdct_enc[mdct_encPos + k] * cs[28 + k]
+									- mdct_enc[mdct_encPos + -1 - k]
+									* ca[20 + k];
+
+							mdct_enc[mdct_encPos + -1 - k] = bu;
+							mdct_enc[mdct_encPos + k] = bd;
+						}
+					}
+				}
+			}
+			wk = w1;
+			wkPos = 286;
+			if (gfc.mode_gr == 1) {
+				for (var i = 0; i < 18; i++) {
+					System.arraycopy(gfc.sb_sample[ch][1][i], 0,
+							gfc.sb_sample[ch][0][i], 0, 32);
+				}
+			}
+		}
+	}
+}
+
+//package mp3;
+
+
+function III_psy_ratio() {
+	this.thm = new III_psy_xmin();
+	this.en = new III_psy_xmin();
+}
+
+
+/**
+ * ENCDELAY The encoder delay.
+ *
+ * Minimum allowed is MDCTDELAY (see below)
+ *
+ * The first 96 samples will be attenuated, so using a value less than 96
+ * will result in corrupt data for the first 96-ENCDELAY samples.
+ *
+ * suggested: 576 set to 1160 to sync with FhG.
+ */
+Encoder.ENCDELAY = 576;
+/**
+ * make sure there is at least one complete frame after the last frame
+ * containing real data
+ *
+ * Using a value of 288 would be sufficient for a a very sophisticated
+ * decoder that can decode granule-by-granule instead of frame by frame. But
+ * lets not assume this, and assume the decoder will not decode frame N
+ * unless it also has data for frame N+1
+ */
+Encoder.POSTDELAY = 1152;
+
+/**
+ * delay of the MDCT used in mdct.c original ISO routines had a delay of
+ * 528! Takehiro's routines:
+ */
+Encoder.MDCTDELAY = 48;
+Encoder.FFTOFFSET = (224 + Encoder.MDCTDELAY);
+
+/**
+ * Most decoders, including the one we use, have a delay of 528 samples.
+ */
+Encoder.DECDELAY = 528;
+
+/**
+ * number of subbands
+ */
+Encoder.SBLIMIT = 32;
+
+/**
+ * parition bands bands
+ */
+Encoder.CBANDS = 64;
+
+/**
+ * number of critical bands/scale factor bands where masking is computed
+ */
+Encoder.SBPSY_l = 21;
+Encoder.SBPSY_s = 12;
+
+/**
+ * total number of scalefactor bands encoded
+ */
+Encoder.SBMAX_l = 22;
+Encoder.SBMAX_s = 13;
+Encoder.PSFB21 = 6;
+Encoder.PSFB12 = 6;
+
+/**
+ * FFT sizes
+ */
+Encoder.BLKSIZE = 1024;
+Encoder.HBLKSIZE = (Encoder.BLKSIZE / 2 + 1);
+Encoder.BLKSIZE_s = 256;
+Encoder.HBLKSIZE_s = (Encoder.BLKSIZE_s / 2 + 1);
+
+Encoder.NORM_TYPE = 0;
+Encoder.START_TYPE = 1;
+Encoder.SHORT_TYPE = 2;
+Encoder.STOP_TYPE = 3;
+
+/**
+ * <PRE>
+ * Mode Extention:
+ * When we are in stereo mode, there are 4 possible methods to store these
+ * two channels. The stereo modes -m? are using a subset of them.
+ *
+ *  -ms: MPG_MD_LR_LR
+ *  -mj: MPG_MD_LR_LR and MPG_MD_MS_LR
+ *  -mf: MPG_MD_MS_LR
+ *  -mi: all
+ * </PRE>
+ */
+Encoder.MPG_MD_LR_LR = 0;
+Encoder.MPG_MD_LR_I = 1;
+Encoder.MPG_MD_MS_LR = 2;
+Encoder.MPG_MD_MS_I = 3;
+
+Encoder.fircoef = [-0.0207887 * 5, -0.0378413 * 5,
+    -0.0432472 * 5, -0.031183 * 5, 7.79609e-18 * 5, 0.0467745 * 5,
+    0.10091 * 5, 0.151365 * 5, 0.187098 * 5];
+
+function Encoder() {
+
+    var FFTOFFSET = Encoder.FFTOFFSET;
+    var MPG_MD_MS_LR = Encoder.MPG_MD_MS_LR;
+    //BitStream bs;
+    //PsyModel psy;
+    //VBRTag vbr;
+    //QuantizePVT qupvt;
+    var bs = null;
+    this.psy = null;
+    var psy = null;
+    var vbr = null;
+    var qupvt = null;
+
+    //public final void setModules(BitStream bs, PsyModel psy, QuantizePVT qupvt,
+    //    VBRTag vbr) {
+    this.setModules = function (_bs, _psy, _qupvt, _vbr) {
+        bs = _bs;
+        this.psy = _psy;
+        psy = _psy;
+        vbr = _vbr;
+        qupvt = _qupvt;
+    };
+
+    var newMDCT = new NewMDCT();
+
+    /***********************************************************************
+     *
+     * encoder and decoder delays
+     *
+     ***********************************************************************/
+
+    /**
+     * <PRE>
+     * layer III enc->dec delay:  1056 (1057?)   (observed)
+     * layer  II enc->dec delay:   480  (481?)   (observed)
+     *
+     * polyphase 256-16             (dec or enc)        = 240
+     * mdct      256+32  (9*32)     (dec or enc)        = 288
+     * total:    512+16
+     *
+     * My guess is that delay of polyphase filterbank is actualy 240.5
+     * (there are technical reasons for this, see postings in mp3encoder).
+     * So total Encode+Decode delay = ENCDELAY + 528 + 1
+     * </PRE>
+     */
+
+
+    /**
+     * auto-adjust of ATH, useful for low volume Gabriel Bouvigne 3 feb 2001
+     *
+     * modifies some values in gfp.internal_flags.ATH (gfc.ATH)
+     */
+//private void adjust_ATH(final LameInternalFlags gfc) {
+    function adjust_ATH(gfc) {
+        var gr2_max, max_pow;
+
+        if (gfc.ATH.useAdjust == 0) {
+            gfc.ATH.adjust = 1.0;
+            /* no adjustment */
+            return;
+        }
+
+        /* jd - 2001 mar 12, 27, jun 30 */
+        /* loudness based on equal loudness curve; */
+        /* use granule with maximum combined loudness */
+        max_pow = gfc.loudness_sq[0][0];
+        gr2_max = gfc.loudness_sq[1][0];
+        if (gfc.channels_out == 2) {
+            abort();//fix cc 精简
+        } else {
+            max_pow += max_pow;
+            gr2_max += gr2_max;
+        }
+        if (gfc.mode_gr == 2) {
+            max_pow = Math.max(max_pow, gr2_max);
+        }
+        max_pow *= 0.5;
+        /* max_pow approaches 1.0 for full band noise */
+
+        /* jd - 2001 mar 31, jun 30 */
+        /* user tuning of ATH adjustment region */
+        max_pow *= gfc.ATH.aaSensitivityP;
+
+        /*
+         * adjust ATH depending on range of maximum value
+         */
+
+        /* jd - 2001 feb27, mar12,20, jun30, jul22 */
+        /* continuous curves based on approximation */
+        /* to GB's original values. */
+        /* For an increase in approximate loudness, */
+        /* set ATH adjust to adjust_limit immediately */
+        /* after a delay of one frame. */
+        /* For a loudness decrease, reduce ATH adjust */
+        /* towards adjust_limit gradually. */
+        /* max_pow is a loudness squared or a power. */
+        if (max_pow > 0.03125) { /* ((1 - 0.000625)/ 31.98) from curve below */
+            if (gfc.ATH.adjust >= 1.0) {
+                gfc.ATH.adjust = 1.0;
+            } else {
+                /* preceding frame has lower ATH adjust; */
+                /* ascend only to the preceding adjust_limit */
+                /* in case there is leading low volume */
+                if (gfc.ATH.adjust < gfc.ATH.adjustLimit) {
+                    gfc.ATH.adjust = gfc.ATH.adjustLimit;
+                }
+            }
+            gfc.ATH.adjustLimit = 1.0;
+        } else { /* adjustment curve */
+            /* about 32 dB maximum adjust (0.000625) */
+            var adj_lim_new = 31.98 * max_pow + 0.000625;
+            if (gfc.ATH.adjust >= adj_lim_new) { /* descend gradually */
+                gfc.ATH.adjust *= adj_lim_new * 0.075 + 0.925;
+                if (gfc.ATH.adjust < adj_lim_new) { /* stop descent */
+                    gfc.ATH.adjust = adj_lim_new;
+                }
+            } else { /* ascend */
+                if (gfc.ATH.adjustLimit >= adj_lim_new) {
+                    gfc.ATH.adjust = adj_lim_new;
+                } else {
+                    /* preceding frame has lower ATH adjust; */
+                    /* ascend only to the preceding adjust_limit */
+                    if (gfc.ATH.adjust < gfc.ATH.adjustLimit) {
+                        gfc.ATH.adjust = gfc.ATH.adjustLimit;
+                    }
+                }
+            }
+            gfc.ATH.adjustLimit = adj_lim_new;
+        }
+    }
+
+    /**
+     * <PRE>
+     *  some simple statistics
+     *
+     *  bitrate index 0: free bitrate . not allowed in VBR mode
+     *  : bitrates, kbps depending on MPEG version
+     *  bitrate index 15: forbidden
+     *
+     *  mode_ext:
+     *  0:  LR
+     *  1:  LR-i
+     *  2:  MS
+     *  3:  MS-i
+     * </PRE>
+     */
+    function updateStats(gfc) {
+        var gr, ch;
+
+        /* count bitrate indices */
+        gfc.bitrate_stereoMode_Hist[gfc.bitrate_index][4]++;
+        gfc.bitrate_stereoMode_Hist[15][4]++;
+
+        /* count 'em for every mode extension in case of 2 channel encoding */
+        if (gfc.channels_out == 2) {
+            abort();//fix cc 精简
+        }
+        for (gr = 0; gr < gfc.mode_gr; ++gr) {
+            for (ch = 0; ch < gfc.channels_out; ++ch) {
+                var bt = gfc.l3_side.tt[gr][ch].block_type | 0;
+                if (gfc.l3_side.tt[gr][ch].mixed_block_flag != 0)
+                    bt = 4;
+                gfc.bitrate_blockType_Hist[gfc.bitrate_index][bt]++;
+                gfc.bitrate_blockType_Hist[gfc.bitrate_index][5]++;
+                gfc.bitrate_blockType_Hist[15][bt]++;
+                gfc.bitrate_blockType_Hist[15][5]++;
+            }
+        }
+    }
+
+    function lame_encode_frame_init(gfp, inbuf) {
+        var gfc = gfp.internal_flags;
+
+        var ch, gr;
+
+        if (gfc.lame_encode_frame_init == 0) {
+            /* prime the MDCT/polyphase filterbank with a short block */
+            var i, j;
+            var primebuff0 = new_float(286 + 1152 + 576);
+            var primebuff1 = new_float(286 + 1152 + 576);
+            gfc.lame_encode_frame_init = 1;
+            for (i = 0, j = 0; i < 286 + 576 * (1 + gfc.mode_gr); ++i) {
+                if (i < 576 * gfc.mode_gr) {
+                    primebuff0[i] = 0;
+                    if (gfc.channels_out == 2)
+                        primebuff1[i] = 0;
+                } else {
+                    primebuff0[i] = inbuf[0][j];
+                    if (gfc.channels_out == 2)
+                        primebuff1[i] = inbuf[1][j];
+                    ++j;
+                }
+            }
+            /* polyphase filtering / mdct */
+            for (gr = 0; gr < gfc.mode_gr; gr++) {
+                for (ch = 0; ch < gfc.channels_out; ch++) {
+                    gfc.l3_side.tt[gr][ch].block_type = Encoder.SHORT_TYPE;
+                }
+            }
+            newMDCT.mdct_sub48(gfc, primebuff0, primebuff1);
+
+            /* check FFT will not use a negative starting offset */
+            /* check if we have enough data for FFT */
+            /* check if we have enough data for polyphase filterbank */
+        }
+
+    }
+
+    /**
+     * <PRE>
+     * encodeframe()           Layer 3
+     *
+     * encode a single frame
+     *
+     *
+     *    lame_encode_frame()
+     *
+     *
+     *                           gr 0            gr 1
+     *    inbuf:           |--------------|--------------|--------------|
+     *
+     *
+     *    Polyphase (18 windows, each shifted 32)
+     *    gr 0:
+     *    window1          <----512---.
+     *    window18                 <----512---.
+     *
+     *    gr 1:
+     *    window1                         <----512---.
+     *    window18                                <----512---.
+     *
+     *
+     *
+     *    MDCT output:  |--------------|--------------|--------------|
+     *
+     *    FFT's                    <---------1024---------.
+     *                                             <---------1024-------.
+     *
+     *
+     *
+     *        inbuf = buffer of PCM data size=MP3 framesize
+     *        encoder acts on inbuf[ch][0], but output is delayed by MDCTDELAY
+     *        so the MDCT coefficints are from inbuf[ch][-MDCTDELAY]
+     *
+     *        psy-model FFT has a 1 granule delay, so we feed it data for the
+     *        next granule.
+     *        FFT is centered over granule:  224+576+224
+     *        So FFT starts at:   576-224-MDCTDELAY
+     *
+     *        MPEG2:  FFT ends at:  BLKSIZE+576-224-MDCTDELAY      (1328)
+     *        MPEG1:  FFT ends at:  BLKSIZE+2*576-224-MDCTDELAY    (1904)
+     *
+     *        MPEG2:  polyphase first window:  [0..511]
+     *                          18th window:   [544..1055]          (1056)
+     *        MPEG1:            36th window:   [1120..1631]         (1632)
+     *                data needed:  512+framesize-32
+     *
+     *        A close look newmdct.c shows that the polyphase filterbank
+     *        only uses data from [0..510] for each window.  Perhaps because the window
+     *        used by the filterbank is zero for the last point, so Takehiro's
+     *        code doesn't bother to compute with it.
+     *
+     *        FFT starts at 576-224-MDCTDELAY (304)  = 576-FFTOFFSET
+     *
+     * </PRE>
+     */
+
+
+    this.lame_encode_mp3_frame = function (gfp, inbuf_l, inbuf_r, mp3buf, mp3bufPos, mp3buf_size) {
+        var mp3count;
+        var masking_LR = new_array_n([2, 2]);
+        /*
+         * LR masking &
+         * energy
+         */
+        masking_LR[0][0] = new III_psy_ratio();
+        masking_LR[0][1] = new III_psy_ratio();
+        masking_LR[1][0] = new III_psy_ratio();
+        masking_LR[1][1] = new III_psy_ratio();
+        var masking_MS = new_array_n([2, 2]);
+        /* MS masking & energy */
+        masking_MS[0][0] = new III_psy_ratio();
+        masking_MS[0][1] = new III_psy_ratio();
+        masking_MS[1][0] = new III_psy_ratio();
+        masking_MS[1][1] = new III_psy_ratio();
+        //III_psy_ratio masking[][];
+        var masking;
+        /* pointer to selected maskings */
+        var inbuf = [null, null];
+        var gfc = gfp.internal_flags;
+
+        var tot_ener = new_float_n([2, 4]);
+        var ms_ener_ratio = [.5, .5];
+        var pe = [[0., 0.], [0., 0.]];
+        var pe_MS = [[0., 0.], [0., 0.]];
+
+//float[][] pe_use;
+        var pe_use;
+
+        var ch, gr;
+
+        inbuf[0] = inbuf_l;
+        inbuf[1] = inbuf_r;
+
+        if (gfc.lame_encode_frame_init == 0) {
+            /* first run? */
+            lame_encode_frame_init(gfp, inbuf);
+
+        }
+
+        /********************** padding *****************************/
+        /**
+         * <PRE>
+         * padding method as described in
+         * "MPEG-Layer3 / Bitstream Syntax and Decoding"
+         * by Martin Sieler, Ralph Sperschneider
+         *
+         * note: there is no padding for the very first frame
+         *
+         * Robert Hegemann 2000-06-22
+         * </PRE>
+         */
+        gfc.padding = 0;
+        if ((gfc.slot_lag -= gfc.frac_SpF) < 0) {//不可精简
+            gfc.slot_lag += gfp.out_samplerate;
+            gfc.padding = 1;
+        }
+
+        /****************************************
+         * Stage 1: psychoacoustic model *
+         ****************************************/
+
+        if (gfc.psymodel != 0) {
+            /*
+             * psychoacoustic model psy model has a 1 granule (576) delay that
+             * we must compensate for (mt 6/99).
+             */
+            var ret;
+            var bufp = [null, null];
+            /* address of beginning of left & right granule */
+            var bufpPos = 0;
+            /* address of beginning of left & right granule */
+            var blocktype = new_int(2);
+
+            for (gr = 0; gr < gfc.mode_gr; gr++) {
+
+                for (ch = 0; ch < gfc.channels_out; ch++) {
+                    bufp[ch] = inbuf[ch];
+                    bufpPos = 576 + gr * 576 - Encoder.FFTOFFSET;
+                }
+                if (gfp.VBR == VbrMode.vbr_mtrh || gfp.VBR == VbrMode.vbr_mt) {
+                    abort();//fix cc 精简
+                } else {
+                    ret = psy.L3psycho_anal_ns(gfp, bufp, bufpPos, gr,
+                        masking_LR, masking_MS, pe[gr], pe_MS[gr],
+                        tot_ener[gr], blocktype);
+                }
+                if (ret != 0)
+                    return -4;
+
+                if (gfp.mode == MPEGMode.JOINT_STEREO) {
+                    abort();//fix cc 精简 stereo
+                }
+
+                /* block type flags */
+                for (ch = 0; ch < gfc.channels_out; ch++) {
+                    var cod_info = gfc.l3_side.tt[gr][ch];
+                    cod_info.block_type = blocktype[ch];
+                    cod_info.mixed_block_flag = 0;
+                }
+            }
+        } else {
+            abort();//fix cc 精简
+        }
+
+        /* auto-adjust of ATH, useful for low volume */
+        adjust_ATH(gfc);
+
+        /****************************************
+         * Stage 2: MDCT *
+         ****************************************/
+
+        /* polyphase filtering / mdct */
+        newMDCT.mdct_sub48(gfc, inbuf[0], inbuf[1]);
+
+        /****************************************
+         * Stage 3: MS/LR decision *
+         ****************************************/
+
+        /* Here will be selected MS or LR coding of the 2 stereo channels */
+        gfc.mode_ext = Encoder.MPG_MD_LR_LR;
+
+        if (gfp.force_ms) {
+            gfc.mode_ext = Encoder.MPG_MD_MS_LR;
+        } else if (gfp.mode == MPEGMode.JOINT_STEREO) {
+            abort();//fix cc 精简 stereo
+        }
+
+        /* bit and noise allocation */
+        if (gfc.mode_ext == MPG_MD_MS_LR) {
+            masking = masking_MS;
+            /* use MS masking */
+            pe_use = pe_MS;
+        } else {
+            masking = masking_LR;
+            /* use LR masking */
+            pe_use = pe;
+        }
+
+        /* copy data for MP3 frame analyzer */
+        if (gfp.analysis && gfc.pinfo != null) {
+            abort();//fix cc 精简
+        }
+
+        /****************************************
+         * Stage 4: quantization loop *
+         ****************************************/
+
+        if (gfp.VBR == VbrMode.vbr_off || gfp.VBR == VbrMode.vbr_abr) {
+
+            var i;
+            var f;
+
+            for (i = 0; i < 18; i++)
+                gfc.nsPsy.pefirbuf[i] = gfc.nsPsy.pefirbuf[i + 1];
+
+            f = 0.0;
+            for (gr = 0; gr < gfc.mode_gr; gr++)
+                for (ch = 0; ch < gfc.channels_out; ch++)
+                    f += pe_use[gr][ch];
+            gfc.nsPsy.pefirbuf[18] = f;
+
+            f = gfc.nsPsy.pefirbuf[9];
+            for (i = 0; i < 9; i++)
+                f += (gfc.nsPsy.pefirbuf[i] + gfc.nsPsy.pefirbuf[18 - i])
+                    * Encoder.fircoef[i];
+
+            f = (670 * 5 * gfc.mode_gr * gfc.channels_out) / f;
+            for (gr = 0; gr < gfc.mode_gr; gr++) {
+                for (ch = 0; ch < gfc.channels_out; ch++) {
+                    pe_use[gr][ch] *= f;
+                }
+            }
+        }
+        gfc.iteration_loop.iteration_loop(gfp, pe_use, ms_ener_ratio, masking);
+
+        /****************************************
+         * Stage 5: bitstream formatting *
+         ****************************************/
+
+        /* write the frame to the bitstream */
+        bs.format_bitstream(gfp);
+
+        /* copy mp3 bit buffer into array */
+        mp3count = bs.copy_buffer(gfc, mp3buf, mp3bufPos, mp3buf_size, 1);
+
+        if (gfp.bWriteVbrTag)
+            vbr.addVbrFrame(gfp);
+
+        if (gfp.analysis && gfc.pinfo != null) {
+            abort();//fix cc 精简
+        }
+
+        updateStats(gfc);
+
+        return mp3count;
+    }
+}
+
+
+//package mp3;
+
+function VBRSeekInfo() {
+    /**
+     * What we have seen so far.
+     */
+    this.sum = 0;
+    /**
+     * How many frames we have seen in this chunk.
+     */
+    this.seen = 0;
+    /**
+     * How many frames we want to collect into one chunk.
+     */
+    this.want = 0;
+    /**
+     * Actual position in our bag.
+     */
+    this.pos = 0;
+    /**
+     * Size of our bag.
+     */
+    this.size = 0;
+    /**
+     * Pointer to our bag.
+     */
+    this.bag = null;
+    this.nVbrNumFrames = 0;
+    this.nBytesWritten = 0;
+    /* VBR tag data */
+    this.TotalFrameSize = 0;
+}
+
+
+
+function IIISideInfo() {
+    this.tt = [[null, null], [null, null]];
+    this.main_data_begin = 0;
+    this.private_bits = 0;
+    this.resvDrain_pre = 0;
+    this.resvDrain_post = 0;
+    this.scfsi = [new_int(4), new_int(4)];
+
+    for (var gr = 0; gr < 2; gr++) {
+        for (var ch = 0; ch < 2; ch++) {
+            this.tt[gr][ch] = new GrInfo();
+        }
+    }
+}
+
+
+function III_psy_xmin() {
+    this.l = new_float(Encoder.SBMAX_l);
+    this.s = new_float_n([Encoder.SBMAX_s, 3]);
+
+    var self = this;
+    this.assign = function (iii_psy_xmin) {
+        System.arraycopy(iii_psy_xmin.l, 0, self.l, 0, Encoder.SBMAX_l);
+        for (var i = 0; i < Encoder.SBMAX_s; i++) {
+            for (var j = 0; j < 3; j++) {
+                self.s[i][j] = iii_psy_xmin.s[i][j];
+            }
+        }
+    }
+}
+
+
+
+//package mp3;
+
+/**
+ * Variables used for --nspsytune
+ *
+ * @author Ken
+ *
+ */
+function NsPsy() {
+    this.last_en_subshort = new_float_n([4, 9]);
+    this.lastAttacks = new_int(4);
+    this.pefirbuf = new_float(19);
+    this.longfact = new_float(Encoder.SBMAX_l);
+    this.shortfact = new_float(Encoder.SBMAX_s);
+
+    /**
+     * short block tuning
+     */
+    this.attackthre = 0.;
+    this.attackthre_s = 0.;
+}
+
+
+
+
+LameInternalFlags.MFSIZE = (3 * 1152 + Encoder.ENCDELAY - Encoder.MDCTDELAY);
+LameInternalFlags.MAX_HEADER_BUF = 256;
+LameInternalFlags.MAX_BITS_PER_CHANNEL = 4095;
+LameInternalFlags.MAX_BITS_PER_GRANULE = 7680;
+LameInternalFlags.BPC = 320;
+
+function LameInternalFlags() {
+    var MAX_HEADER_LEN = 40;
+
+
+    /********************************************************************
+     * internal variables NOT set by calling program, and should not be *
+     * modified by the calling program *
+     ********************************************************************/
+
+    /**
+     * Some remarks to the Class_ID field: The Class ID is an Identifier for a
+     * pointer to this struct. It is very unlikely that a pointer to
+     * lame_global_flags has the same 32 bits in it's structure (large and other
+     * special properties, for instance prime).
+     *
+     * To test that the structure is right and initialized, use: if ( gfc .
+     * Class_ID == LAME_ID ) ... Other remark: If you set a flag to 0 for uninit
+     * data and 1 for init data, the right test should be "if (flag == 1)" and
+     * NOT "if (flag)". Unintended modification of this element will be
+     * otherwise misinterpreted as an init.
+     */
+    this.Class_ID = 0;
+
+    this.lame_encode_frame_init = 0;
+    this.iteration_init_init = 0;
+    this.fill_buffer_resample_init = 0;
+
+    //public float mfbuf[][] = new float[2][MFSIZE];
+    this.mfbuf = new_float_n([2, LameInternalFlags.MFSIZE]);
+
+    /**
+     * granules per frame
+     */
+    this.mode_gr = 0;
+    /**
+     * number of channels in the input data stream (PCM or decoded PCM)
+     */
+    this.channels_in = 0;
+    /**
+     * number of channels in the output data stream (not used for decoding)
+     */
+    this.channels_out = 0;
+    /**
+     * input_samp_rate/output_samp_rate
+     */
+        //public double resample_ratio;
+    this.resample_ratio = 0.;
+
+    this.mf_samples_to_encode = 0;
+    this.mf_size = 0;
+    /**
+     * min bitrate index
+     */
+    this.VBR_min_bitrate = 0;
+    /**
+     * max bitrate index
+     */
+    this.VBR_max_bitrate = 0;
+    this.bitrate_index = 0;
+    this.samplerate_index = 0;
+    this.mode_ext = 0;
+
+    /* lowpass and highpass filter control */
+    /**
+     * normalized frequency bounds of passband
+     */
+    this.lowpass1 = 0.;
+    this.lowpass2 = 0.;
+    /**
+     * normalized frequency bounds of passband
+     */
+    this.highpass1 = 0.;
+    this.highpass2 = 0.;
+
+    /**
+     * 0 = none 1 = ISO AAC model 2 = allow scalefac_select=1
+     */
+    this.noise_shaping = 0;
+
+    /**
+     * 0 = ISO model: amplify all distorted bands<BR>
+     * 1 = amplify within 50% of max (on db scale)<BR>
+     * 2 = amplify only most distorted band<BR>
+     * 3 = method 1 and refine with method 2<BR>
+     */
+    this.noise_shaping_amp = 0;
+    /**
+     * 0 = no substep<BR>
+     * 1 = use substep shaping at last step(VBR only)<BR>
+     * (not implemented yet)<BR>
+     * 2 = use substep inside loop<BR>
+     * 3 = use substep inside loop and last step<BR>
+     */
+    this.substep_shaping = 0;
+
+    /**
+     * 1 = gpsycho. 0 = none
+     */
+    this.psymodel = 0;
+    /**
+     * 0 = stop at over=0, all scalefacs amplified or<BR>
+     * a scalefac has reached max value<BR>
+     * 1 = stop when all scalefacs amplified or a scalefac has reached max value<BR>
+     * 2 = stop when all scalefacs amplified
+     */
+    this.noise_shaping_stop = 0;
+
+    /**
+     * 0 = no, 1 = yes
+     */
+    this.subblock_gain = 0;
+    /**
+     * 0 = no. 1=outside loop 2=inside loop(slow)
+     */
+    this.use_best_huffman = 0;
+
+    /**
+     * 0 = stop early after 0 distortion found. 1 = full search
+     */
+    this.full_outer_loop = 0;
+
+    //public IIISideInfo l3_side = new IIISideInfo();
+    this.l3_side = new IIISideInfo();
+    this.ms_ratio = new_float(2);
+
+    /* used for padding */
+    /**
+     * padding for the current frame?
+     */
+    this.padding = 0;
+    this.frac_SpF = 0;
+    this.slot_lag = 0;
+
+    /**
+     * optional ID3 tags
+     */
+        //public ID3TagSpec tag_spec;
+    this.tag_spec = null;
+    this.nMusicCRC = 0;
+
+    /* variables used by Quantize */
+    //public int OldValue[] = new int[2];
+    this.OldValue = new_int(2);
+    //public int CurrentStep[] = new int[2];
+    this.CurrentStep = new_int(2);
+
+    this.masking_lower = 0.;
+    //public int bv_scf[] = new int[576];
+    this.bv_scf = new_int(576);
+    //public int pseudohalf[] = new int[L3Side.SFBMAX];
+    this.pseudohalf = new_int(L3Side.SFBMAX);
+
+    /**
+     * will be set in lame_init_params
+     */
+    this.sfb21_extra = false;
+
+    /* BPC = maximum number of filter convolution windows to precompute */
+    //public float[][] inbuf_old = new float[2][];
+    this.inbuf_old = new Array(2);
+    //public float[][] blackfilt = new float[2 * BPC + 1][];
+    this.blackfilt = new Array(2 * LameInternalFlags.BPC + 1);
+    //public double itime[] = new double[2];
+    this.itime = new_double(2);
+    this.sideinfo_len = 0;
+
+    /* variables for newmdct.c */
+    //public float sb_sample[][][][] = new float[2][2][18][Encoder.SBLIMIT];
+    this.sb_sample = new_float_n([2, 2, 18, Encoder.SBLIMIT]);
+    this.amp_filter = new_float(32);
+
+    /* variables for BitStream */
+
+    /**
+     * <PRE>
+     * mpeg1: buffer=511 bytes  smallest frame: 96-38(sideinfo)=58
+     * max number of frames in reservoir:  8
+     * mpeg2: buffer=255 bytes.  smallest frame: 24-23bytes=1
+     * with VBR, if you are encoding all silence, it is possible to
+     * have 8kbs/24khz frames with 1byte of data each, which means we need
+     * to buffer up to 255 headers!
+     * </PRE>
+     */
+    /**
+     * also, max_header_buf has to be a power of two
+     */
+    /**
+     * max size of header is 38
+     */
+
+    function Header() {
+        this.write_timing = 0;
+        this.ptr = 0;
+        //public byte buf[] = new byte[MAX_HEADER_LEN];
+        this.buf = new_byte(MAX_HEADER_LEN);
+    }
+
+    this.header = new Array(LameInternalFlags.MAX_HEADER_BUF);
+
+    this.h_ptr = 0;
+    this.w_ptr = 0;
+    this.ancillary_flag = 0;
+
+    /* variables for Reservoir */
+    /**
+     * in bits
+     */
+    this.ResvSize = 0;
+    /**
+     * in bits
+     */
+    this.ResvMax = 0;
+
+    //public ScaleFac scalefac_band = new ScaleFac();
+    this.scalefac_band = new ScaleFac();
+
+    /* daa from PsyModel */
+    /* The static variables "r", "phi_sav", "new", "old" and "oldest" have */
+    /* to be remembered for the unpredictability measure. For "r" and */
+    /* "phi_sav", the first index from the left is the channel select and */
+    /* the second index is the "age" of the data. */
+    this.minval_l = new_float(Encoder.CBANDS);
+    this.minval_s = new_float(Encoder.CBANDS);
+    this.nb_1 = new_float_n([4, Encoder.CBANDS]);
+    this.nb_2 = new_float_n([4, Encoder.CBANDS]);
+    this.nb_s1 = new_float_n([4, Encoder.CBANDS]);
+    this.nb_s2 = new_float_n([4, Encoder.CBANDS]);
+    this.s3_ss = null;
+    this.s3_ll = null;
+    this.decay = 0.;
+
+    //public III_psy_xmin[] thm = new III_psy_xmin[4];
+    //public III_psy_xmin[] en = new III_psy_xmin[4];
+    this.thm = new Array(4);
+    this.en = new Array(4);
+
+    /**
+     * fft and energy calculation
+     */
+    this.tot_ener = new_float(4);
+
+    /* loudness calculation (for adaptive threshold of hearing) */
+    /**
+     * loudness^2 approx. per granule and channel
+     */
+    this.loudness_sq = new_float_n([2, 2]);
+    /**
+     * account for granule delay of L3psycho_anal
+     */
+    this.loudness_sq_save = new_float(2);
+
+    /**
+     * Scale Factor Bands
+     */
+    this.mld_l = new_float(Encoder.SBMAX_l);
+    this.mld_s = new_float(Encoder.SBMAX_s);
+    this.bm_l = new_int(Encoder.SBMAX_l);
+    this.bo_l = new_int(Encoder.SBMAX_l);
+    this.bm_s = new_int(Encoder.SBMAX_s);
+    this.bo_s = new_int(Encoder.SBMAX_s);
+    this.npart_l = 0;
+    this.npart_s = 0;
+
+    this.s3ind = new_int_n([Encoder.CBANDS, 2]);
+    this.s3ind_s = new_int_n([Encoder.CBANDS, 2]);
+
+    this.numlines_s = new_int(Encoder.CBANDS);
+    this.numlines_l = new_int(Encoder.CBANDS);
+    this.rnumlines_l = new_float(Encoder.CBANDS);
+    this.mld_cb_l = new_float(Encoder.CBANDS);
+    this.mld_cb_s = new_float(Encoder.CBANDS);
+    this.numlines_s_num1 = 0;
+    this.numlines_l_num1 = 0;
+
+    /* ratios */
+    this.pe = new_float(4);
+    this.ms_ratio_s_old = 0.;
+    this.ms_ratio_l_old = 0.;
+    this.ms_ener_ratio_old = 0.;
+
+    /**
+     * block type
+     */
+    this.blocktype_old = new_int(2);
+
+    /**
+     * variables used for --nspsytune
+     */
+    this.nsPsy = new NsPsy();
+
+    /**
+     * used for Xing VBR header
+     */
+    this.VBR_seek_table = new VBRSeekInfo();
+
+    /**
+     * all ATH related stuff
+     */
+        //public ATH ATH;
+    this.ATH = null;
+
+    this.PSY = null;
+
+    this.nogap_total = 0;
+    this.nogap_current = 0;
+
+    /* ReplayGain */
+    this.decode_on_the_fly = true;
+    this.findReplayGain = true;
+    this.findPeakSample = true;
+    this.PeakSample = 0.;
+    this.RadioGain = 0;
+    this.AudiophileGain = 0;
+    //public ReplayGain rgdata;
+    this.rgdata = null;
+
+    /**
+     * gain change required for preventing clipping
+     */
+    this.noclipGainChange = 0;
+    /**
+     * user-specified scale factor required for preventing clipping
+     */
+    this.noclipScale = 0.;
+
+    /* simple statistics */
+    this.bitrate_stereoMode_Hist = new_int_n([16, 4 + 1]);
+    /**
+     * norm/start/short/stop/mixed(short)/sum
+     */
+    this.bitrate_blockType_Hist = new_int_n([16, 4 + 1 + 1]);
+
+    //public PlottingData pinfo;
+    //public MPGLib.mpstr_tag hip;
+    this.pinfo = null;
+    this.hip = null;
+
+    this.in_buffer_nsamples = 0;
+    //public float[] in_buffer_0;
+    //public float[] in_buffer_1;
+    this.in_buffer_0 = null;
+    this.in_buffer_1 = null;
+
+    //public IIterationLoop iteration_loop;
+    this.iteration_loop = null;
+
+    for (var i = 0; i < this.en.length; i++) {
+        this.en[i] = new III_psy_xmin();
+    }
+    for (var i = 0; i < this.thm.length; i++) {
+        this.thm[i] = new III_psy_xmin();
+    }
+    for (var i = 0; i < this.header.length; i++) {
+        this.header[i] = new Header();
+    }
+
+}
+
+
+
+function FFT() {
+
+    var window = new_float(Encoder.BLKSIZE);
+    var window_s = new_float(Encoder.BLKSIZE_s / 2);
+
+    var costab = [
+        9.238795325112867e-01, 3.826834323650898e-01,
+        9.951847266721969e-01, 9.801714032956060e-02,
+        9.996988186962042e-01, 2.454122852291229e-02,
+        9.999811752826011e-01, 6.135884649154475e-03
+    ];
+
+    function fht(fz, fzPos, n) {
+        var tri = 0;
+        var k4;
+        var fi;
+        var gi;
+
+        n <<= 1;
+        /* to get BLKSIZE, because of 3DNow! ASM routine */
+        var fn = fzPos + n;
+        k4 = 4;
+        do {
+            var s1, c1;
+            var i, k1, k2, k3, kx;
+            kx = k4 >> 1;
+            k1 = k4;
+            k2 = k4 << 1;
+            k3 = k2 + k1;
+            k4 = k2 << 1;
+            fi = fzPos;
+            gi = fi + kx;
+            do {
+                var f0, f1, f2, f3;
+                f1 = fz[fi + 0] - fz[fi + k1];
+                f0 = fz[fi + 0] + fz[fi + k1];
+                f3 = fz[fi + k2] - fz[fi + k3];
+                f2 = fz[fi + k2] + fz[fi + k3];
+                fz[fi + k2] = f0 - f2;
+                fz[fi + 0] = f0 + f2;
+                fz[fi + k3] = f1 - f3;
+                fz[fi + k1] = f1 + f3;
+                f1 = fz[gi + 0] - fz[gi + k1];
+                f0 = fz[gi + 0] + fz[gi + k1];
+                f3 = (Util.SQRT2 * fz[gi + k3]);
+                f2 = (Util.SQRT2 * fz[gi + k2]);
+                fz[gi + k2] = f0 - f2;
+                fz[gi + 0] = f0 + f2;
+                fz[gi + k3] = f1 - f3;
+                fz[gi + k1] = f1 + f3;
+                gi += k4;
+                fi += k4;
+            } while (fi < fn);
+            c1 = costab[tri + 0];
+            s1 = costab[tri + 1];
+            for (i = 1; i < kx; i++) {
+                var c2, s2;
+                c2 = 1 - (2 * s1) * s1;
+                s2 = (2 * s1) * c1;
+                fi = fzPos + i;
+                gi = fzPos + k1 - i;
+                do {
+                    var a, b, g0, f0, f1, g1, f2, g2, f3, g3;
+                    b = s2 * fz[fi + k1] - c2 * fz[gi + k1];
+                    a = c2 * fz[fi + k1] + s2 * fz[gi + k1];
+                    f1 = fz[fi + 0] - a;
+                    f0 = fz[fi + 0] + a;
+                    g1 = fz[gi + 0] - b;
+                    g0 = fz[gi + 0] + b;
+                    b = s2 * fz[fi + k3] - c2 * fz[gi + k3];
+                    a = c2 * fz[fi + k3] + s2 * fz[gi + k3];
+                    f3 = fz[fi + k2] - a;
+                    f2 = fz[fi + k2] + a;
+                    g3 = fz[gi + k2] - b;
+                    g2 = fz[gi + k2] + b;
+                    b = s1 * f2 - c1 * g3;
+                    a = c1 * f2 + s1 * g3;
+                    fz[fi + k2] = f0 - a;
+                    fz[fi + 0] = f0 + a;
+                    fz[gi + k3] = g1 - b;
+                    fz[gi + k1] = g1 + b;
+                    b = c1 * g2 - s1 * f3;
+                    a = s1 * g2 + c1 * f3;
+                    fz[gi + k2] = g0 - a;
+                    fz[gi + 0] = g0 + a;
+                    fz[fi + k3] = f1 - b;
+                    fz[fi + k1] = f1 + b;
+                    gi += k4;
+                    fi += k4;
+                } while (fi < fn);
+                c2 = c1;
+                c1 = c2 * costab[tri + 0] - s1 * costab[tri + 1];
+                s1 = c2 * costab[tri + 1] + s1 * costab[tri + 0];
+            }
+            tri += 2;
+        } while (k4 < n);
+    }
+
+    var rv_tbl = [0x00, 0x80, 0x40,
+        0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10,
+        0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70,
+        0xf0, 0x08, 0x88, 0x48, 0xc8, 0x28,
+        0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58,
+        0xd8, 0x38, 0xb8, 0x78, 0xf8, 0x04,
+        0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64,
+        0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34,
+        0xb4, 0x74, 0xf4, 0x0c, 0x8c, 0x4c,
+        0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c,
+        0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c,
+        0xfc, 0x02, 0x82, 0x42, 0xc2, 0x22,
+        0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52,
+        0xd2, 0x32, 0xb2, 0x72, 0xf2, 0x0a,
+        0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a,
+        0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a,
+        0xba, 0x7a, 0xfa, 0x06, 0x86, 0x46,
+        0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16,
+        0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76,
+        0xf6, 0x0e, 0x8e, 0x4e, 0xce, 0x2e,
+        0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e,
+        0xde, 0x3e, 0xbe, 0x7e, 0xfe];
+
+    this.fft_short = function (gfc, x_real, chn, buffer, bufPos) {
+        for (var b = 0; b < 3; b++) {
+            var x = Encoder.BLKSIZE_s / 2;
+            var k = 0xffff & ((576 / 3) * (b + 1));
+            var j = Encoder.BLKSIZE_s / 8 - 1;
+            do {
+                var f0, f1, f2, f3, w;
+                var i = rv_tbl[j << 2] & 0xff;
+
+                f0 = window_s[i] * buffer[chn][bufPos + i + k];
+                w = window_s[0x7f - i] * buffer[chn][bufPos + i + k + 0x80];
+                f1 = f0 - w;
+                f0 = f0 + w;
+                f2 = window_s[i + 0x40] * buffer[chn][bufPos + i + k + 0x40];
+                w = window_s[0x3f - i] * buffer[chn][bufPos + i + k + 0xc0];
+                f3 = f2 - w;
+                f2 = f2 + w;
+
+                x -= 4;
+                x_real[b][x + 0] = f0 + f2;
+                x_real[b][x + 2] = f0 - f2;
+                x_real[b][x + 1] = f1 + f3;
+                x_real[b][x + 3] = f1 - f3;
+
+                f0 = window_s[i + 0x01] * buffer[chn][bufPos + i + k + 0x01];
+                w = window_s[0x7e - i] * buffer[chn][bufPos + i + k + 0x81];
+                f1 = f0 - w;
+                f0 = f0 + w;
+                f2 = window_s[i + 0x41] * buffer[chn][bufPos + i + k + 0x41];
+                w = window_s[0x3e - i] * buffer[chn][bufPos + i + k + 0xc1];
+                f3 = f2 - w;
+                f2 = f2 + w;
+
+                x_real[b][x + Encoder.BLKSIZE_s / 2 + 0] = f0 + f2;
+                x_real[b][x + Encoder.BLKSIZE_s / 2 + 2] = f0 - f2;
+                x_real[b][x + Encoder.BLKSIZE_s / 2 + 1] = f1 + f3;
+                x_real[b][x + Encoder.BLKSIZE_s / 2 + 3] = f1 - f3;
+            } while (--j >= 0);
+
+            fht(x_real[b], x, Encoder.BLKSIZE_s / 2);
+            /* BLKSIZE_s/2 because of 3DNow! ASM routine */
+            /* BLKSIZE/2 because of 3DNow! ASM routine */
+        }
+    }
+
+    this.fft_long = function (gfc, y, chn, buffer, bufPos) {
+        var jj = Encoder.BLKSIZE / 8 - 1;
+        var x = Encoder.BLKSIZE / 2;
+
+        do {
+            var f0, f1, f2, f3, w;
+            var i = rv_tbl[jj] & 0xff;
+            f0 = window[i] * buffer[chn][bufPos + i];
+            w = window[i + 0x200] * buffer[chn][bufPos + i + 0x200];
+            f1 = f0 - w;
+            f0 = f0 + w;
+            f2 = window[i + 0x100] * buffer[chn][bufPos + i + 0x100];
+            w = window[i + 0x300] * buffer[chn][bufPos + i + 0x300];
+            f3 = f2 - w;
+            f2 = f2 + w;
+
+            x -= 4;
+            y[x + 0] = f0 + f2;
+            y[x + 2] = f0 - f2;
+            y[x + 1] = f1 + f3;
+            y[x + 3] = f1 - f3;
+
+            f0 = window[i + 0x001] * buffer[chn][bufPos + i + 0x001];
+            w = window[i + 0x201] * buffer[chn][bufPos + i + 0x201];
+            f1 = f0 - w;
+            f0 = f0 + w;
+            f2 = window[i + 0x101] * buffer[chn][bufPos + i + 0x101];
+            w = window[i + 0x301] * buffer[chn][bufPos + i + 0x301];
+            f3 = f2 - w;
+            f2 = f2 + w;
+
+            y[x + Encoder.BLKSIZE / 2 + 0] = f0 + f2;
+            y[x + Encoder.BLKSIZE / 2 + 2] = f0 - f2;
+            y[x + Encoder.BLKSIZE / 2 + 1] = f1 + f3;
+            y[x + Encoder.BLKSIZE / 2 + 3] = f1 - f3;
+        } while (--jj >= 0);
+
+        fht(y, x, Encoder.BLKSIZE / 2);
+        /* BLKSIZE/2 because of 3DNow! ASM routine */
+    }
+
+    this.init_fft = function (gfc) {
+        /* The type of window used here will make no real difference, but */
+        /*
+         * in the interest of merging nspsytune stuff - switch to blackman
+         * window
+         */
+        for (var i = 0; i < Encoder.BLKSIZE; i++)
+            /* blackman window */
+            window[i] = (0.42 - 0.5 * Math.cos(2 * Math.PI * (i + .5)
+                / Encoder.BLKSIZE) + 0.08 * Math.cos(4 * Math.PI * (i + .5)
+                / Encoder.BLKSIZE));
+
+        for (var i = 0; i < Encoder.BLKSIZE_s / 2; i++)
+            window_s[i] = (0.5 * (1.0 - Math.cos(2.0 * Math.PI
+                * (i + 0.5) / Encoder.BLKSIZE_s)));
+
+    }
+
+}
+
+/*
+ *      psymodel.c
+ *
+ *      Copyright (c) 1999-2000 Mark Taylor
+ *      Copyright (c) 2001-2002 Naoki Shibata
+ *      Copyright (c) 2000-2003 Takehiro Tominaga
+ *      Copyright (c) 2000-2008 Robert Hegemann
+ *      Copyright (c) 2000-2005 Gabriel Bouvigne
+ *      Copyright (c) 2000-2005 Alexander Leidinger
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+/* $Id: PsyModel.java,v 1.27 2011/05/24 20:48:06 kenchis Exp $ */
+
+
+/*
+ PSYCHO ACOUSTICS
+
+
+ This routine computes the psycho acoustics, delayed by one granule.
+
+ Input: buffer of PCM data (1024 samples).
+
+ This window should be centered over the 576 sample granule window.
+ The routine will compute the psycho acoustics for
+ this granule, but return the psycho acoustics computed
+ for the *previous* granule.  This is because the block
+ type of the previous granule can only be determined
+ after we have computed the psycho acoustics for the following
+ granule.
+
+ Output:  maskings and energies for each scalefactor band.
+ block type, PE, and some correlation measures.
+ The PE is used by CBR modes to determine if extra bits
+ from the bit reservoir should be used.  The correlation
+ measures are used to determine mid/side or regular stereo.
+ */
+/*
+ Notation:
+
+ barks:  a non-linear frequency scale.  Mapping from frequency to
+ barks is given by freq2bark()
+
+ scalefactor bands: The spectrum (frequencies) are broken into
+ SBMAX "scalefactor bands".  Thes bands
+ are determined by the MPEG ISO spec.  In
+ the noise shaping/quantization code, we allocate
+ bits among the partition bands to achieve the
+ best possible quality
+
+ partition bands:   The spectrum is also broken into about
+ 64 "partition bands".  Each partition
+ band is about .34 barks wide.  There are about 2-5
+ partition bands for each scalefactor band.
+
+ LAME computes all psycho acoustic information for each partition
+ band.  Then at the end of the computations, this information
+ is mapped to scalefactor bands.  The energy in each scalefactor
+ band is taken as the sum of the energy in all partition bands
+ which overlap the scalefactor band.  The maskings can be computed
+ in the same way (and thus represent the average masking in that band)
+ or by taking the minmum value multiplied by the number of
+ partition bands used (which represents a minimum masking in that band).
+ */
+/*
+ The general outline is as follows:
+
+ 1. compute the energy in each partition band
+ 2. compute the tonality in each partition band
+ 3. compute the strength of each partion band "masker"
+ 4. compute the masking (via the spreading function applied to each masker)
+ 5. Modifications for mid/side masking.
+
+ Each partition band is considiered a "masker".  The strength
+ of the i'th masker in band j is given by:
+
+ s3(bark(i)-bark(j))*strength(i)
+
+ The strength of the masker is a function of the energy and tonality.
+ The more tonal, the less masking.  LAME uses a simple linear formula
+ (controlled by NMT and TMN) which says the strength is given by the
+ energy divided by a linear function of the tonality.
+ */
+/*
+ s3() is the "spreading function".  It is given by a formula
+ determined via listening tests.
+
+ The total masking in the j'th partition band is the sum over
+ all maskings i.  It is thus given by the convolution of
+ the strength with s3(), the "spreading function."
+
+ masking(j) = sum_over_i  s3(i-j)*strength(i)  = s3 o strength
+
+ where "o" = convolution operator.  s3 is given by a formula determined
+ via listening tests.  It is normalized so that s3 o 1 = 1.
+
+ Note: instead of a simple convolution, LAME also has the
+ option of using "additive masking"
+
+ The most critical part is step 2, computing the tonality of each
+ partition band.  LAME has two tonality estimators.  The first
+ is based on the ISO spec, and measures how predictiable the
+ signal is over time.  The more predictable, the more tonal.
+ The second measure is based on looking at the spectrum of
+ a single granule.  The more peaky the spectrum, the more
+ tonal.  By most indications, the latter approach is better.
+
+ Finally, in step 5, the maskings for the mid and side
+ channel are possibly increased.  Under certain circumstances,
+ noise in the mid & side channels is assumed to also
+ be masked by strong maskers in the L or R channels.
+
+
+ Other data computed by the psy-model:
+
+ ms_ratio        side-channel / mid-channel masking ratio (for previous granule)
+ ms_ratio_next   side-channel / mid-channel masking ratio for this granule
+
+ percep_entropy[2]     L and R values (prev granule) of PE - A measure of how
+ much pre-echo is in the previous granule
+ percep_entropy_MS[2]  mid and side channel values (prev granule) of percep_entropy
+ energy[4]             L,R,M,S energy in each channel, prev granule
+ blocktype_d[2]        block type to use for previous granule
+ */
+//package mp3;
+
+//import java.util.Arrays;
+
+
+function PsyModel() {
+
+    var fft = new FFT();
+
+    var LOG10 = 2.30258509299404568402;
+
+    var rpelev = 2;
+    var rpelev2 = 16;
+    var rpelev_s = 2;
+    var rpelev2_s = 16;
+
+    /* size of each partition band, in barks: */
+    var DELBARK = .34;
+
+    /* tuned for output level (sensitive to energy scale) */
+    var VO_SCALE = (1. / (14752 * 14752) / (Encoder.BLKSIZE / 2));
+
+    var temporalmask_sustain_sec = 0.01;
+
+    var NS_PREECHO_ATT0 = 0.8;
+    var NS_PREECHO_ATT1 = 0.6;
+    var NS_PREECHO_ATT2 = 0.3;
+
+    var NS_MSFIX = 3.5;
+
+    var NSATTACKTHRE = 4.4;
+    var NSATTACKTHRE_S = 25;
+
+    var NSFIRLEN = 21;
+
+    /* size of each partition band, in barks: */
+    var LN_TO_LOG10 = 0.2302585093;
+
+    /*fix cc 精简 func NON_LINEAR_SCALE_ENERGY(x) {
+        return x;
+    }*/
+
+    /**
+     * <PRE>
+     *       L3psycho_anal.  Compute psycho acoustics.
+     *
+     *       Data returned to the calling program must be delayed by one
+     *       granule.
+     *
+     *       This is done in two places.
+     *       If we do not need to know the blocktype, the copying
+     *       can be done here at the top of the program: we copy the data for
+     *       the last granule (computed during the last call) before it is
+     *       overwritten with the new data.  It looks like this:
+     *
+     *       0. static psymodel_data
+     *       1. calling_program_data = psymodel_data
+     *       2. compute psymodel_data
+     *
+     *       For data which needs to know the blocktype, the copying must be
+     *       done at the end of this loop, and the old values must be saved:
+     *
+     *       0. static psymodel_data_old
+     *       1. compute psymodel_data
+     *       2. compute possible block type of this granule
+     *       3. compute final block type of previous granule based on #2.
+     *       4. calling_program_data = psymodel_data_old
+     *       5. psymodel_data_old = psymodel_data
+     *     psycho_loudness_approx
+     *       jd - 2001 mar 12
+     *    in:  energy   - BLKSIZE/2 elements of frequency magnitudes ^ 2
+     *         gfp      - uses out_samplerate, ATHtype (also needed for ATHformula)
+     *    returns: loudness^2 approximation, a positive value roughly tuned for a value
+     *             of 1.0 for signals near clipping.
+     *    notes:   When calibrated, feeding this function binary white noise at sample
+     *             values +32767 or -32768 should return values that approach 3.
+     *             ATHformula is used to approximate an equal loudness curve.
+     *    future:  Data indicates that the shape of the equal loudness curve varies
+     *             with intensity.  This function might be improved by using an equal
+     *             loudness curve shaped for typical playback levels (instead of the
+     *             ATH, that is shaped for the threshold).  A flexible realization might
+     *             simply bend the existing ATH curve to achieve the desired shape.
+     *             However, the potential gain may not be enough to justify an effort.
+     * </PRE>
+     */
+    function psycho_loudness_approx(energy, gfc) {
+        var loudness_power = 0.0;
+        /* apply weights to power in freq. bands */
+        for (var i = 0; i < Encoder.BLKSIZE / 2; ++i)
+            loudness_power += energy[i] * gfc.ATH.eql_w[i];
+        loudness_power *= VO_SCALE;
+
+        return loudness_power;
+    }
+
+    function compute_ffts(gfp, fftenergy, fftenergy_s, wsamp_l, wsamp_lPos, wsamp_s, wsamp_sPos, gr_out, chn, buffer, bufPos) {
+        var gfc = gfp.internal_flags;
+        if (chn < 2) {
+            fft.fft_long(gfc, wsamp_l[wsamp_lPos], chn, buffer, bufPos);
+            fft.fft_short(gfc, wsamp_s[wsamp_sPos], chn, buffer, bufPos);
+        }
+        /* FFT data for mid and side channel is derived from L & R */
+        else if (chn == 2) {
+            abort();//fix cc 精简
+        }
+
+        /*********************************************************************
+         * compute energies
+         *********************************************************************/
+        fftenergy[0] = /*fix NON_LINEAR_SCALE_ENERGY*/(wsamp_l[wsamp_lPos + 0][0]);
+        fftenergy[0] *= fftenergy[0];
+
+        for (var j = Encoder.BLKSIZE / 2 - 1; j >= 0; --j) {
+            var re = (wsamp_l[wsamp_lPos + 0])[Encoder.BLKSIZE / 2 - j];
+            var im = (wsamp_l[wsamp_lPos + 0])[Encoder.BLKSIZE / 2 + j];
+            fftenergy[Encoder.BLKSIZE / 2 - j] = /*fix NON_LINEAR_SCALE_ENERGY*/((re
+                * re + im * im) * 0.5);
+        }
+        for (var b = 2; b >= 0; --b) {
+            fftenergy_s[b][0] = (wsamp_s[wsamp_sPos + 0])[b][0];
+            fftenergy_s[b][0] *= fftenergy_s[b][0];
+            for (var j = Encoder.BLKSIZE_s / 2 - 1; j >= 0; --j) {
+                var re = (wsamp_s[wsamp_sPos + 0])[b][Encoder.BLKSIZE_s
+                / 2 - j];
+                var im = (wsamp_s[wsamp_sPos + 0])[b][Encoder.BLKSIZE_s
+                / 2 + j];
+                fftenergy_s[b][Encoder.BLKSIZE_s / 2 - j] = /*fix NON_LINEAR_SCALE_ENERGY*/((re
+                    * re + im * im) * 0.5);
+            }
+        }
+        /* total energy */
+        {
+            var totalenergy = 0.0;
+            for (var j = 11; j < Encoder.HBLKSIZE; j++)
+                totalenergy += fftenergy[j];
+
+            gfc.tot_ener[chn] = totalenergy;
+        }
+
+        if (gfp.analysis) {
+            abort();//fix cc 精简
+        }
+
+        /*********************************************************************
+         * compute loudness approximation (used for ATH auto-level adjustment)
+         *********************************************************************/
+        if (gfp.athaa_loudapprox == 2 && chn < 2) {
+            // no loudness for mid/side ch
+            gfc.loudness_sq[gr_out][chn] = gfc.loudness_sq_save[chn];
+            gfc.loudness_sq_save[chn] = psycho_loudness_approx(fftenergy, gfc);
+        }
+    }
+
+    /* mask_add optimization */
+    /* init the limit values used to avoid computing log in mask_add when it is not necessary */
+
+    /**
+     * <PRE>
+     *  For example, with i = 10*log10(m2/m1)/10*16         (= log10(m2/m1)*16)
+     *
+     * abs(i)>8 is equivalent (as i is an integer) to
+     * abs(i)>=9
+     * i>=9 || i<=-9
+     * equivalent to (as i is the biggest integer smaller than log10(m2/m1)*16
+     * or the smallest integer bigger than log10(m2/m1)*16 depending on the sign of log10(m2/m1)*16)
+     * log10(m2/m1)>=9/16 || log10(m2/m1)<=-9/16
+     * exp10 is strictly increasing thus this is equivalent to
+     * m2/m1 >= 10^(9/16) || m2/m1<=10^(-9/16) which are comparisons to constants
+     * </PRE>
+     */
+
+    /**
+     * as in if(i>8)
+     */
+    var I1LIMIT = 8;
+    /**
+     * as in if(i>24) . changed 23
+     */
+    var I2LIMIT = 23;
+    /**
+     * as in if(m<15)
+     */
+    var MLIMIT = 15;
+
+    var ma_max_i1;
+    var ma_max_i2;
+    var ma_max_m;
+
+    /**
+     * This is the masking table:<BR>
+     * According to tonality, values are going from 0dB (TMN) to 9.3dB (NMT).<BR>
+     * After additive masking computation, 8dB are added, so final values are
+     * going from 8dB to 17.3dB
+     *
+     * pow(10, -0.0..-0.6)
+     */
+    var tab = [1.0, 0.79433, 0.63096, 0.63096,
+        0.63096, 0.63096, 0.63096, 0.25119, 0.11749];
+
+    function init_mask_add_max_values() {
+        ma_max_i1 = Math.pow(10, (I1LIMIT + 1) / 16.0);
+        ma_max_i2 = Math.pow(10, (I2LIMIT + 1) / 16.0);
+        ma_max_m = Math.pow(10, (MLIMIT) / 10.0);
+    }
+
+    var table1 = [3.3246 * 3.3246,
+        3.23837 * 3.23837, 3.15437 * 3.15437, 3.00412 * 3.00412,
+        2.86103 * 2.86103, 2.65407 * 2.65407, 2.46209 * 2.46209,
+        2.284 * 2.284, 2.11879 * 2.11879, 1.96552 * 1.96552,
+        1.82335 * 1.82335, 1.69146 * 1.69146, 1.56911 * 1.56911,
+        1.46658 * 1.46658, 1.37074 * 1.37074, 1.31036 * 1.31036,
+        1.25264 * 1.25264, 1.20648 * 1.20648, 1.16203 * 1.16203,
+        1.12765 * 1.12765, 1.09428 * 1.09428, 1.0659 * 1.0659,
+        1.03826 * 1.03826, 1.01895 * 1.01895, 1];
+
+    var table2 = [1.33352 * 1.33352,
+        1.35879 * 1.35879, 1.38454 * 1.38454, 1.39497 * 1.39497,
+        1.40548 * 1.40548, 1.3537 * 1.3537, 1.30382 * 1.30382,
+        1.22321 * 1.22321, 1.14758 * 1.14758, 1];
+
+    var table3 = [2.35364 * 2.35364,
+        2.29259 * 2.29259, 2.23313 * 2.23313, 2.12675 * 2.12675,
+        2.02545 * 2.02545, 1.87894 * 1.87894, 1.74303 * 1.74303,
+        1.61695 * 1.61695, 1.49999 * 1.49999, 1.39148 * 1.39148,
+        1.29083 * 1.29083, 1.19746 * 1.19746, 1.11084 * 1.11084,
+        1.03826 * 1.03826];
+
+    /**
+     * addition of simultaneous masking Naoki Shibata 2000/7
+     */
+    function mask_add(m1, m2, kk, b, gfc, shortblock) {
+        var ratio;
+
+        if (m2 > m1) {
+            if (m2 < (m1 * ma_max_i2))
+                ratio = m2 / m1;
+            else
+                return (m1 + m2);
+        } else {
+            if (m1 >= (m2 * ma_max_i2))
+                return (m1 + m2);
+            ratio = m1 / m2;
+        }
+
+        /* Should always be true, just checking */
+
+        m1 += m2;
+        //if (((long)(b + 3) & 0xffffffff) <= 3 + 3) {
+        if ((b + 3) <= 3 + 3) {
+            /* approximately, 1 bark = 3 partitions */
+            /* 65% of the cases */
+            /* originally 'if(i > 8)' */
+            if (ratio >= ma_max_i1) {
+                /* 43% of the total */
+                return m1;
+            }
+
+            /* 22% of the total */
+            var i = 0 | (Util.FAST_LOG10_X(ratio, 16.0));
+            return m1 * table2[i];
+        }
+
+        /**
+         * <PRE>
+         * m<15 equ log10((m1+m2)/gfc.ATH.cb[k])<1.5
+         * equ (m1+m2)/gfc.ATH.cb[k]<10^1.5
+         * equ (m1+m2)<10^1.5 * gfc.ATH.cb[k]
+         * </PRE>
+         */
+        var i = 0 | Util.FAST_LOG10_X(ratio, 16.0);
+        if (shortblock != 0) {
+            m2 = gfc.ATH.cb_s[kk] * gfc.ATH.adjust;
+        } else {
+            m2 = gfc.ATH.cb_l[kk] * gfc.ATH.adjust;
+        }
+        if (m1 < ma_max_m * m2) {
+            /* 3% of the total */
+            /* Originally if (m > 0) { */
+            if (m1 > m2) {
+                var f, r;
+
+                f = 1.0;
+                if (i <= 13)
+                    f = table3[i];
+
+                r = Util.FAST_LOG10_X(m1 / m2, 10.0 / 15.0);
+                return m1 * ((table1[i] - f) * r + f);
+            }
+
+            if (i > 13)
+                return m1;
+
+            return m1 * table3[i];
+        }
+
+        /* 10% of total */
+        return m1 * table1[i];
+    }
+
+    //fix cc 精简
+
+    /**
+     * short block threshold calculation (part 2)
+     *
+     * partition band bo_s[sfb] is at the transition from scalefactor band sfb
+     * to the next one sfb+1; enn and thmm have to be split between them
+     */
+    function convert_partition2scalefac_s(gfc, eb, thr, chn, sblock) {
+        var sb, b;
+        var enn = 0.0;
+        var thmm = 0.0;
+        for (sb = b = 0; sb < Encoder.SBMAX_s; ++b, ++sb) {
+            var bo_s_sb = gfc.bo_s[sb];
+            var npart_s = gfc.npart_s;
+            var b_lim = bo_s_sb < npart_s ? bo_s_sb : npart_s;
+            while (b < b_lim) {
+                // iff failed, it may indicate some index error elsewhere
+                enn += eb[b];
+                thmm += thr[b];
+                b++;
+            }
+            gfc.en[chn].s[sb][sblock] = enn;
+            gfc.thm[chn].s[sb][sblock] = thmm;
+
+            if (b >= npart_s) {
+                ++sb;
+                break;
+            }
+            // iff failed, it may indicate some index error elsewhere
+            {
+                /* at transition sfb . sfb+1 */
+                var w_curr = gfc.PSY.bo_s_weight[sb];
+                var w_next = 1.0 - w_curr;
+                enn = w_curr * eb[b];
+                thmm = w_curr * thr[b];
+                gfc.en[chn].s[sb][sblock] += enn;
+                gfc.thm[chn].s[sb][sblock] += thmm;
+                enn = w_next * eb[b];
+                thmm = w_next * thr[b];
+            }
+        }
+        /* zero initialize the rest */
+        for (; sb < Encoder.SBMAX_s; ++sb) {
+            gfc.en[chn].s[sb][sblock] = 0;
+            gfc.thm[chn].s[sb][sblock] = 0;
+        }
+    }
+
+    /**
+     * longblock threshold calculation (part 2)
+     */
+    function convert_partition2scalefac_l(gfc, eb, thr, chn) {
+        var sb, b;
+        var enn = 0.0;
+        var thmm = 0.0;
+        for (sb = b = 0; sb < Encoder.SBMAX_l; ++b, ++sb) {
+            var bo_l_sb = gfc.bo_l[sb];
+            var npart_l = gfc.npart_l;
+            var b_lim = bo_l_sb < npart_l ? bo_l_sb : npart_l;
+            while (b < b_lim) {
+                // iff failed, it may indicate some index error elsewhere
+                enn += eb[b];
+                thmm += thr[b];
+                b++;
+            }
+            gfc.en[chn].l[sb] = enn;
+            gfc.thm[chn].l[sb] = thmm;
+
+            if (b >= npart_l) {
+                ++sb;
+                break;
+            }
+            {
+                /* at transition sfb . sfb+1 */
+                var w_curr = gfc.PSY.bo_l_weight[sb];
+                var w_next = 1.0 - w_curr;
+                enn = w_curr * eb[b];
+                thmm = w_curr * thr[b];
+                gfc.en[chn].l[sb] += enn;
+                gfc.thm[chn].l[sb] += thmm;
+                enn = w_next * eb[b];
+                thmm = w_next * thr[b];
+            }
+        }
+        /* zero initialize the rest */
+        for (; sb < Encoder.SBMAX_l; ++sb) {
+            gfc.en[chn].l[sb] = 0;
+            gfc.thm[chn].l[sb] = 0;
+        }
+    }
+
+    function compute_masking_s(gfp, fftenergy_s, eb, thr, chn, sblock) {
+        var gfc = gfp.internal_flags;
+        var j, b;
+
+        for (b = j = 0; b < gfc.npart_s; ++b) {
+            var ebb = 0, m = 0;
+            var n = gfc.numlines_s[b];
+            for (var i = 0; i < n; ++i, ++j) {
+                var el = fftenergy_s[sblock][j];
+                ebb += el;
+                if (m < el)
+                    m = el;
+            }
+            eb[b] = ebb;
+        }
+        for (j = b = 0; b < gfc.npart_s; b++) {
+            var kk = gfc.s3ind_s[b][0];
+            var ecb = gfc.s3_ss[j++] * eb[kk];
+            ++kk;
+            while (kk <= gfc.s3ind_s[b][1]) {
+                ecb += gfc.s3_ss[j] * eb[kk];
+                ++j;
+                ++kk;
+            }
+
+            { /* limit calculated threshold by previous granule */
+                var x = rpelev_s * gfc.nb_s1[chn][b];
+                thr[b] = Math.min(ecb, x);
+            }
+            if (gfc.blocktype_old[chn & 1] == Encoder.SHORT_TYPE) {
+                /* limit calculated threshold by even older granule */
+                var x = rpelev2_s * gfc.nb_s2[chn][b];
+                var y = thr[b];
+                thr[b] = Math.min(x, y);
+            }
+
+            gfc.nb_s2[chn][b] = gfc.nb_s1[chn][b];
+            gfc.nb_s1[chn][b] = ecb;
+        }
+        for (; b <= Encoder.CBANDS; ++b) {
+            eb[b] = 0;
+            thr[b] = 0;
+        }
+    }
+
+    function block_type_set(gfp, uselongblock, blocktype_d, blocktype) {
+        var gfc = gfp.internal_flags;
+
+        if (gfp.short_blocks == ShortBlock.short_block_coupled
+                /* force both channels to use the same block type */
+                /* this is necessary if the frame is to be encoded in ms_stereo. */
+                /* But even without ms_stereo, FhG does this */
+            && !(uselongblock[0] != 0 && uselongblock[1] != 0))
+            uselongblock[0] = uselongblock[1] = 0;
+
+        /*
+         * update the blocktype of the previous granule, since it depends on
+         * what happend in this granule
+         */
+        for (var chn = 0; chn < gfc.channels_out; chn++) {
+            blocktype[chn] = Encoder.NORM_TYPE;
+            /* disable short blocks */
+            if (gfp.short_blocks == ShortBlock.short_block_dispensed)
+                uselongblock[chn] = 1;
+            if (gfp.short_blocks == ShortBlock.short_block_forced)
+                uselongblock[chn] = 0;
+
+            if (uselongblock[chn] != 0) {
+                /* no attack : use long blocks */
+                if (gfc.blocktype_old[chn] == Encoder.SHORT_TYPE)
+                    blocktype[chn] = Encoder.STOP_TYPE;
+            } else {
+                /* attack : use short blocks */
+                blocktype[chn] = Encoder.SHORT_TYPE;
+                if (gfc.blocktype_old[chn] == Encoder.NORM_TYPE) {
+                    gfc.blocktype_old[chn] = Encoder.START_TYPE;
+                }
+                if (gfc.blocktype_old[chn] == Encoder.STOP_TYPE)
+                    gfc.blocktype_old[chn] = Encoder.SHORT_TYPE;
+            }
+
+            blocktype_d[chn] = gfc.blocktype_old[chn];
+            // value returned to calling program
+            gfc.blocktype_old[chn] = blocktype[chn];
+            // save for next call to l3psy_anal
+        }
+    }
+
+    function NS_INTERP(x, y, r) {
+        /* was pow((x),(r))*pow((y),1-(r)) */
+        if (r >= 1.0) {
+            /* 99.7% of the time */
+            return x;
+        }
+        if (r <= 0.0)
+            return y;
+        if (y > 0.0) {
+            /* rest of the time */
+            return (Math.pow(x / y, r) * y);
+        }
+        /* never happens */
+        return 0.0;
+    }
+
+    /**
+     * these values are tuned only for 44.1kHz...
+     */
+    var regcoef_s = [11.8, 13.6, 17.2, 32, 46.5,
+        51.3, 57.5, 67.1, 71.5, 84.6, 97.6, 130,
+        /* 255.8 */
+    ];
+
+    function pecalc_s(mr, masking_lower) {
+        var pe_s = 1236.28 / 4;
+        for (var sb = 0; sb < Encoder.SBMAX_s - 1; sb++) {
+            for (var sblock = 0; sblock < 3; sblock++) {
+                var thm = mr.thm.s[sb][sblock];
+                if (thm > 0.0) {
+                    var x = thm * masking_lower;
+                    var en = mr.en.s[sb][sblock];
+                    if (en > x) {
+                        if (en > x * 1e10) {
+                            pe_s += regcoef_s[sb] * (10.0 * LOG10);
+                        } else {
+                            pe_s += regcoef_s[sb] * Util.FAST_LOG10(en / x);
+                        }
+                    }
+                }
+            }
+        }
+
+        return pe_s;
+    }
+
+    /**
+     * these values are tuned only for 44.1kHz...
+     */
+    var regcoef_l = [6.8, 5.8, 5.8, 6.4, 6.5, 9.9,
+        12.1, 14.4, 15, 18.9, 21.6, 26.9, 34.2, 40.2, 46.8, 56.5,
+        60.7, 73.9, 85.7, 93.4, 126.1,
+        /* 241.3 */
+    ];
+
+    function pecalc_l(mr, masking_lower) {
+        var pe_l = 1124.23 / 4;
+        for (var sb = 0; sb < Encoder.SBMAX_l - 1; sb++) {
+            var thm = mr.thm.l[sb];
+            if (thm > 0.0) {
+                var x = thm * masking_lower;
+                var en = mr.en.l[sb];
+                if (en > x) {
+                    if (en > x * 1e10) {
+                        pe_l += regcoef_l[sb] * (10.0 * LOG10);
+                    } else {
+                        pe_l += regcoef_l[sb] * Util.FAST_LOG10(en / x);
+                    }
+                }
+            }
+        }
+        return pe_l;
+    }
+
+    function calc_energy(gfc, fftenergy, eb, max, avg) {
+        var b, j;
+
+        for (b = j = 0; b < gfc.npart_l; ++b) {
+            var ebb = 0, m = 0;
+            var i;
+            for (i = 0; i < gfc.numlines_l[b]; ++i, ++j) {
+                var el = fftenergy[j];
+                ebb += el;
+                if (m < el)
+                    m = el;
+            }
+            eb[b] = ebb;
+            max[b] = m;
+            avg[b] = ebb * gfc.rnumlines_l[b];
+        }
+    }
+
+    function calc_mask_index_l(gfc, max, avg, mask_idx) {
+        var last_tab_entry = tab.length - 1;
+        var b = 0;
+        var a = avg[b] + avg[b + 1];
+        if (a > 0.0) {
+            var m = max[b];
+            if (m < max[b + 1])
+                m = max[b + 1];
+            a = 20.0 * (m * 2.0 - a)
+                / (a * (gfc.numlines_l[b] + gfc.numlines_l[b + 1] - 1));
+            var k = 0 | a;
+            if (k > last_tab_entry)
+                k = last_tab_entry;
+            mask_idx[b] = k;
+        } else {
+            mask_idx[b] = 0;
+        }
+
+        for (b = 1; b < gfc.npart_l - 1; b++) {
+            a = avg[b - 1] + avg[b] + avg[b + 1];
+            if (a > 0.0) {
+                var m = max[b - 1];
+                if (m < max[b])
+                    m = max[b];
+                if (m < max[b + 1])
+                    m = max[b + 1];
+                a = 20.0
+                    * (m * 3.0 - a)
+                    / (a * (gfc.numlines_l[b - 1] + gfc.numlines_l[b]
+                    + gfc.numlines_l[b + 1] - 1));
+                var k = 0 | a;
+                if (k > last_tab_entry)
+                    k = last_tab_entry;
+                mask_idx[b] = k;
+            } else {
+                mask_idx[b] = 0;
+            }
+        }
+
+        a = avg[b - 1] + avg[b];
+        if (a > 0.0) {
+            var m = max[b - 1];
+            if (m < max[b])
+                m = max[b];
+            a = 20.0 * (m * 2.0 - a)
+                / (a * (gfc.numlines_l[b - 1] + gfc.numlines_l[b] - 1));
+            var k = 0 | a;
+            if (k > last_tab_entry)
+                k = last_tab_entry;
+            mask_idx[b] = k;
+        } else {
+            mask_idx[b] = 0;
+        }
+    }
+
+    var fircoef = [
+        -8.65163e-18 * 2, -0.00851586 * 2, -6.74764e-18 * 2, 0.0209036 * 2,
+        -3.36639e-17 * 2, -0.0438162 * 2, -1.54175e-17 * 2, 0.0931738 * 2,
+        -5.52212e-17 * 2, -0.313819 * 2
+    ];
+
+    this.L3psycho_anal_ns = function (gfp, buffer, bufPos, gr_out, masking_ratio, masking_MS_ratio, percep_entropy, percep_MS_entropy, energy, blocktype_d) {
+        /*
+         * to get a good cache performance, one has to think about the sequence,
+         * in which the variables are used.
+         */
+        var gfc = gfp.internal_flags;
+
+        /* fft and energy calculation */
+        var wsamp_L = new_float_n([2, Encoder.BLKSIZE]);
+        var wsamp_S = new_float_n([2, 3, Encoder.BLKSIZE_s]);
+
+        /* convolution */
+        var eb_l = new_float(Encoder.CBANDS + 1);
+        var eb_s = new_float(Encoder.CBANDS + 1);
+        var thr = new_float(Encoder.CBANDS + 2);
+
+        /* block type */
+        var blocktype = new_int(2), uselongblock = new_int(2);
+
+        /* usual variables like loop indices, etc.. */
+        var numchn, chn;
+        var b, i, j, k;
+        var sb, sblock;
+
+        /* variables used for --nspsytune */
+        var ns_hpfsmpl = new_float_n([2, 576]);
+        var pcfact;
+        var mask_idx_l = new_int(Encoder.CBANDS + 2), mask_idx_s = new_int(Encoder.CBANDS + 2);
+
+        Arrays.fill(mask_idx_s, 0);
+
+        numchn = gfc.channels_out;
+        /* chn=2 and 3 = Mid and Side channels */
+        if (gfp.mode == MPEGMode.JOINT_STEREO)
+            numchn = 4;
+
+        if (gfp.VBR == VbrMode.vbr_off)
+            pcfact = gfc.ResvMax == 0 ? 0 : ( gfc.ResvSize)
+            / gfc.ResvMax * 0.5;
+        else if (gfp.VBR == VbrMode.vbr_rh || gfp.VBR == VbrMode.vbr_mtrh
+            || gfp.VBR == VbrMode.vbr_mt) {
+            pcfact = 0.6;
+        } else
+            pcfact = 1.0;
+
+        /**********************************************************************
+         * Apply HPF of fs/4 to the input signal. This is used for attack
+         * detection / handling.
+         **********************************************************************/
+        /* Don't copy the input buffer into a temporary buffer */
+        /* unroll the loop 2 times */
+        for (chn = 0; chn < gfc.channels_out; chn++) {
+            /* apply high pass filter of fs/4 */
+            var firbuf = buffer[chn];
+            var firbufPos = bufPos + 576 - 350 - NSFIRLEN + 192;
+            for (i = 0; i < 576; i++) {
+                var sum1, sum2;
+                sum1 = firbuf[firbufPos + i + 10];
+                sum2 = 0.0;
+                for (j = 0; j < ((NSFIRLEN - 1) / 2) - 1; j += 2) {
+                    sum1 += fircoef[j]
+                        * (firbuf[firbufPos + i + j] + firbuf[firbufPos + i
+                        + NSFIRLEN - j]);
+                    sum2 += fircoef[j + 1]
+                        * (firbuf[firbufPos + i + j + 1] + firbuf[firbufPos
+                        + i + NSFIRLEN - j - 1]);
+                }
+                ns_hpfsmpl[chn][i] = sum1 + sum2;
+            }
+            masking_ratio[gr_out][chn].en.assign(gfc.en[chn]);
+            masking_ratio[gr_out][chn].thm.assign(gfc.thm[chn]);
+            if (numchn > 2) {
+                abort();//fix cc 精简
+            }
+        }
+
+        for (chn = 0; chn < numchn; chn++) {
+            var wsamp_l;
+            var wsamp_s;
+            var en_subshort = new_float(12);
+            var en_short = [0, 0, 0, 0];
+            var attack_intensity = new_float(12);
+            var ns_uselongblock = 1;
+            var attackThreshold;
+            var max = new_float(Encoder.CBANDS), avg = new_float(Encoder.CBANDS);
+            var ns_attacks = [0, 0, 0, 0];
+            var fftenergy = new_float(Encoder.HBLKSIZE);
+            var fftenergy_s = new_float_n([3, Encoder.HBLKSIZE_s]);
+
+            /*
+             * rh 20040301: the following loops do access one off the limits so
+             * I increase the array dimensions by one and initialize the
+             * accessed values to zero
+             */
+
+            /***************************************************************
+             * determine the block type (window type)
+             ***************************************************************/
+            /* calculate energies of each sub-shortblocks */
+            for (i = 0; i < 3; i++) {
+                en_subshort[i] = gfc.nsPsy.last_en_subshort[chn][i + 6];
+                attack_intensity[i] = en_subshort[i]
+                    / gfc.nsPsy.last_en_subshort[chn][i + 4];
+                en_short[0] += en_subshort[i];
+            }
+
+            if (chn == 2) {
+                abort();//fix cc 精简
+            }
+            {
+                var pf = ns_hpfsmpl[chn & 1];
+                var pfPos = 0;
+                for (i = 0; i < 9; i++) {
+                    var pfe = pfPos + 576 / 9;
+                    var p = 1.;
+                    for (; pfPos < pfe; pfPos++)
+                        if (p < Math.abs(pf[pfPos]))
+                            p = Math.abs(pf[pfPos]);
+
+                    gfc.nsPsy.last_en_subshort[chn][i] = en_subshort[i + 3] = p;
+                    en_short[1 + i / 3] += p;
+                    if (p > en_subshort[i + 3 - 2]) {
+                        p = p / en_subshort[i + 3 - 2];
+                    } else if (en_subshort[i + 3 - 2] > p * 10.0) {
+                        p = en_subshort[i + 3 - 2] / (p * 10.0);
+                    } else
+                        p = 0.0;
+                    attack_intensity[i + 3] = p;
+                }
+            }
+
+            if (gfp.analysis) {
+                abort();//fix cc 精简
+            }
+
+            /* compare energies between sub-shortblocks */
+            attackThreshold = (chn == 3) ? gfc.nsPsy.attackthre_s
+                : gfc.nsPsy.attackthre;
+            for (i = 0; i < 12; i++)
+                if (0 == ns_attacks[i / 3]
+                    && attack_intensity[i] > attackThreshold)
+                    ns_attacks[i / 3] = (i % 3) + 1;
+
+            /*
+             * should have energy change between short blocks, in order to avoid
+             * periodic signals
+             */
+            for (i = 1; i < 4; i++) {
+                var ratio;
+                if (en_short[i - 1] > en_short[i]) {
+                    ratio = en_short[i - 1] / en_short[i];
+                } else {
+                    ratio = en_short[i] / en_short[i - 1];
+                }
+                if (ratio < 1.7) {
+                    ns_attacks[i] = 0;
+                    if (i == 1)
+                        ns_attacks[0] = 0;
+                }
+            }
+
+            if (ns_attacks[0] != 0 && gfc.nsPsy.lastAttacks[chn] != 0)
+                ns_attacks[0] = 0;
+
+            if (gfc.nsPsy.lastAttacks[chn] == 3
+                || (ns_attacks[0] + ns_attacks[1] + ns_attacks[2] + ns_attacks[3]) != 0) {
+                ns_uselongblock = 0;
+
+                if (ns_attacks[1] != 0 && ns_attacks[0] != 0)
+                    ns_attacks[1] = 0;
+                if (ns_attacks[2] != 0 && ns_attacks[1] != 0)
+                    ns_attacks[2] = 0;
+                if (ns_attacks[3] != 0 && ns_attacks[2] != 0)
+                    ns_attacks[3] = 0;
+            }
+
+            if (chn < 2) {
+                uselongblock[chn] = ns_uselongblock;
+            } else {
+                abort();//fix cc 精简
+            }
+
+            /*
+             * there is a one granule delay. Copy maskings computed last call
+             * into masking_ratio to return to calling program.
+             */
+            energy[chn] = gfc.tot_ener[chn];
+
+            /*********************************************************************
+             * compute FFTs
+             *********************************************************************/
+            wsamp_s = wsamp_S;
+            wsamp_l = wsamp_L;
+            compute_ffts(gfp, fftenergy, fftenergy_s, wsamp_l, (chn & 1),
+                wsamp_s, (chn & 1), gr_out, chn, buffer, bufPos);
+
+            /*********************************************************************
+             * Calculate the energy and the tonality of each partition.
+             *********************************************************************/
+            calc_energy(gfc, fftenergy, eb_l, max, avg);
+            calc_mask_index_l(gfc, max, avg, mask_idx_l);
+            /* compute masking thresholds for short blocks */
+            for (sblock = 0; sblock < 3; sblock++) {
+                var enn, thmm;
+                compute_masking_s(gfp, fftenergy_s, eb_s, thr, chn, sblock);
+                convert_partition2scalefac_s(gfc, eb_s, thr, chn, sblock);
+                /**** short block pre-echo control ****/
+                for (sb = 0; sb < Encoder.SBMAX_s; sb++) {
+                    thmm = gfc.thm[chn].s[sb][sblock];
+
+                    thmm *= NS_PREECHO_ATT0;
+                    if (ns_attacks[sblock] >= 2 || ns_attacks[sblock + 1] == 1) {
+                        var idx = (sblock != 0) ? sblock - 1 : 2;
+                        var p = NS_INTERP(gfc.thm[chn].s[sb][idx], thmm,
+                            NS_PREECHO_ATT1 * pcfact);
+                        thmm = Math.min(thmm, p);
+                    }
+
+                    if (ns_attacks[sblock] == 1) {
+                        var idx = (sblock != 0) ? sblock - 1 : 2;
+                        var p = NS_INTERP(gfc.thm[chn].s[sb][idx], thmm,
+                            NS_PREECHO_ATT2 * pcfact);
+                        thmm = Math.min(thmm, p);
+                    } else if ((sblock != 0 && ns_attacks[sblock - 1] == 3)
+                        || (sblock == 0 && gfc.nsPsy.lastAttacks[chn] == 3)) {
+                        var idx = (sblock != 2) ? sblock + 1 : 0;
+                        var p = NS_INTERP(gfc.thm[chn].s[sb][idx], thmm,
+                            NS_PREECHO_ATT2 * pcfact);
+                        thmm = Math.min(thmm, p);
+                    }
+
+                    /* pulse like signal detection for fatboy.wav and so on */
+                    enn = en_subshort[sblock * 3 + 3]
+                        + en_subshort[sblock * 3 + 4]
+                        + en_subshort[sblock * 3 + 5];
+                    if (en_subshort[sblock * 3 + 5] * 6 < enn) {
+                        thmm *= 0.5;
+                        if (en_subshort[sblock * 3 + 4] * 6 < enn)
+                            thmm *= 0.5;
+                    }
+
+                    gfc.thm[chn].s[sb][sblock] = thmm;
+                }
+            }
+            gfc.nsPsy.lastAttacks[chn] = ns_attacks[2];
+
+            /*********************************************************************
+             * convolve the partitioned energy and unpredictability with the
+             * spreading function, s3_l[b][k]
+             ********************************************************************/
+            k = 0;
+            {
+                for (b = 0; b < gfc.npart_l; b++) {
+                    /*
+                     * convolve the partitioned energy with the spreading
+                     * function
+                     */
+                    var kk = gfc.s3ind[b][0];
+                    var eb2 = eb_l[kk] * tab[mask_idx_l[kk]];
+                    var ecb = gfc.s3_ll[k++] * eb2;
+                    while (++kk <= gfc.s3ind[b][1]) {
+                        eb2 = eb_l[kk] * tab[mask_idx_l[kk]];
+                        ecb = mask_add(ecb, gfc.s3_ll[k++] * eb2, kk, kk - b,
+                            gfc, 0);
+                    }
+                    ecb *= 0.158489319246111;
+                    /* pow(10,-0.8) */
+
+                    /**** long block pre-echo control ****/
+                    /**
+                     * <PRE>
+                     * dont use long block pre-echo control if previous granule was
+                     * a short block.  This is to avoid the situation:
+                     * frame0:  quiet (very low masking)
+                     * frame1:  surge  (triggers short blocks)
+                     * frame2:  regular frame.  looks like pre-echo when compared to
+                     *          frame0, but all pre-echo was in frame1.
+                     * </PRE>
+                     */
+                    /*
+                     * chn=0,1 L and R channels
+                     *
+                     * chn=2,3 S and M channels.
+                     */
+
+                    if (gfc.blocktype_old[chn & 1] == Encoder.SHORT_TYPE)
+                        thr[b] = ecb;
+                    else
+                        thr[b] = NS_INTERP(
+                            Math.min(ecb, Math.min(rpelev
+                                * gfc.nb_1[chn][b], rpelev2
+                                * gfc.nb_2[chn][b])), ecb, pcfact);
+
+                    gfc.nb_2[chn][b] = gfc.nb_1[chn][b];
+                    gfc.nb_1[chn][b] = ecb;
+                }
+            }
+            for (; b <= Encoder.CBANDS; ++b) {
+                eb_l[b] = 0;
+                thr[b] = 0;
+            }
+            /* compute masking thresholds for long blocks */
+            convert_partition2scalefac_l(gfc, eb_l, thr, chn);
+        }
+        /* end loop over chn */
+
+        if (gfp.mode == MPEGMode.STEREO || gfp.mode == MPEGMode.JOINT_STEREO) {
+            abort();//fix cc 精简 stereo
+        }
+
+        if (gfp.mode == MPEGMode.JOINT_STEREO) {
+            abort();//fix cc 精简 stereo
+        }
+
+        /***************************************************************
+         * determine final block type
+         ***************************************************************/
+        block_type_set(gfp, uselongblock, blocktype_d, blocktype);
+
+        /*********************************************************************
+         * compute the value of PE to return ... no delay and advance
+         *********************************************************************/
+        for (chn = 0; chn < numchn; chn++) {
+            var ppe;
+            var ppePos = 0;
+            var type;
+            var mr;
+
+            if (chn > 1) {
+                abort();//fix cc 精简
+            } else {
+                ppe = percep_entropy;
+                ppePos = 0;
+                type = blocktype_d[chn];
+                mr = masking_ratio[gr_out][chn];
+            }
+
+            if (type == Encoder.SHORT_TYPE)
+                ppe[ppePos + chn] = pecalc_s(mr, gfc.masking_lower);
+            else
+                ppe[ppePos + chn] = pecalc_l(mr, gfc.masking_lower);
+
+            if (gfp.analysis)
+                gfc.pinfo.pe[gr_out][chn] = ppe[ppePos + chn];
+
+        }
+        return 0;
+    }
+
+    //fix cc 精简
+
+    /**
+     *   The spreading function.  Values returned in units of energy
+     */
+    function s3_func(bark) {
+        var tempx, x, tempy, temp;
+        tempx = bark;
+        if (tempx >= 0)
+            tempx *= 3;
+        else
+            tempx *= 1.5;
+
+        if (tempx >= 0.5 && tempx <= 2.5) {
+            temp = tempx - 0.5;
+            x = 8.0 * (temp * temp - 2.0 * temp);
+        } else
+            x = 0.0;
+        tempx += 0.474;
+        tempy = 15.811389 + 7.5 * tempx - 17.5
+            * Math.sqrt(1.0 + tempx * tempx);
+
+        if (tempy <= -60.0)
+            return 0.0;
+
+        tempx = Math.exp((x + tempy) * LN_TO_LOG10);
+
+        /**
+         * <PRE>
+         * Normalization.  The spreading function should be normalized so that:
+         * +inf
+         * /
+         * |  s3 [ bark ]  d(bark)   =  1
+         * /
+         * -inf
+         * </PRE>
+         */
+        tempx /= .6609193;
+        return tempx;
+    }
+
+    /**
+     * see for example "Zwicker: Psychoakustik, 1982; ISBN 3-540-11401-7
+     */
+    function freq2bark(freq) {
+        /* input: freq in hz output: barks */
+        if (freq < 0)
+            freq = 0;
+        freq = freq * 0.001;
+        return 13.0 * Math.atan(.76 * freq) + 3.5
+            * Math.atan(freq * freq / (7.5 * 7.5));
+    }
+
+    function init_numline(numlines, bo, bm, bval, bval_width, mld, bo_w, sfreq, blksize, scalepos, deltafreq, sbmax) {
+        var b_frq = new_float(Encoder.CBANDS + 1);
+        var sample_freq_frac = sfreq / (sbmax > 15 ? 2 * 576 : 2 * 192);
+        var partition = new_int(Encoder.HBLKSIZE);
+        var i;
+        sfreq /= blksize;
+        var j = 0;
+        var ni = 0;
+        /* compute numlines, the number of spectral lines in each partition band */
+        /* each partition band should be about DELBARK wide. */
+        for (i = 0; i < Encoder.CBANDS; i++) {
+            var bark1;
+            var j2;
+            bark1 = freq2bark(sfreq * j);
+
+            b_frq[i] = sfreq * j;
+
+            for (j2 = j; freq2bark(sfreq * j2) - bark1 < DELBARK
+            && j2 <= blksize / 2; j2++)
+                ;
+
+            numlines[i] = j2 - j;
+            ni = i + 1;
+
+            while (j < j2) {
+                partition[j++] = i;
+            }
+            if (j > blksize / 2) {
+                j = blksize / 2;
+                ++i;
+                break;
+            }
+        }
+        b_frq[i] = sfreq * j;
+
+        for (var sfb = 0; sfb < sbmax; sfb++) {
+            var i1, i2, start, end;
+            var arg;
+            start = scalepos[sfb];
+            end = scalepos[sfb + 1];
+
+            i1 = 0 | Math.floor(.5 + deltafreq * (start - .5));
+            if (i1 < 0)
+                i1 = 0;
+            i2 = 0 | Math.floor(.5 + deltafreq * (end - .5));
+
+            if (i2 > blksize / 2)
+                i2 = blksize / 2;
+
+            bm[sfb] = (partition[i1] + partition[i2]) / 2;
+            bo[sfb] = partition[i2];
+            var f_tmp = sample_freq_frac * end;
+            /*
+             * calculate how much of this band belongs to current scalefactor
+             * band
+             */
+            bo_w[sfb] = (f_tmp - b_frq[bo[sfb]])
+                / (b_frq[bo[sfb] + 1] - b_frq[bo[sfb]]);
+            if (bo_w[sfb] < 0) {
+                bo_w[sfb] = 0;
+            } else {
+                if (bo_w[sfb] > 1) {
+                    bo_w[sfb] = 1;
+                }
+            }
+            /* setup stereo demasking thresholds */
+            /* formula reverse enginerred from plot in paper */
+            arg = freq2bark(sfreq * scalepos[sfb] * deltafreq);
+            arg = ( Math.min(arg, 15.5) / 15.5);
+
+            mld[sfb] = Math.pow(10.0,
+                1.25 * (1 - Math.cos(Math.PI * arg)) - 2.5);
+        }
+
+        /* compute bark values of each critical band */
+        j = 0;
+        for (var k = 0; k < ni; k++) {
+            var w = numlines[k];
+            var bark1, bark2;
+
+            bark1 = freq2bark(sfreq * (j));
+            bark2 = freq2bark(sfreq * (j + w - 1));
+            bval[k] = .5 * (bark1 + bark2);
+
+            bark1 = freq2bark(sfreq * (j - .5));
+            bark2 = freq2bark(sfreq * (j + w - .5));
+            bval_width[k] = bark2 - bark1;
+            j += w;
+        }
+
+        return ni;
+    }
+
+    function init_s3_values(s3ind, npart, bval, bval_width, norm, use_old_s3) {
+        var s3 = new_float_n([Encoder.CBANDS, Encoder.CBANDS]);
+        /*
+         * The s3 array is not linear in the bark scale.
+         *
+         * bval[x] should be used to get the bark value.
+         */
+        var j;
+        var numberOfNoneZero = 0;
+
+        /**
+         * <PRE>
+         * s[i][j], the value of the spreading function,
+         * centered at band j (masker), for band i (maskee)
+         *
+         * i.e.: sum over j to spread into signal barkval=i
+         * NOTE: i and j are used opposite as in the ISO docs
+         * </PRE>
+         */
+        if (use_old_s3) {
+            for (var i = 0; i < npart; i++) {
+                for (j = 0; j < npart; j++) {
+                    var v = s3_func(bval[i] - bval[j]) * bval_width[j];
+                    s3[i][j] = v * norm[i];
+                }
+            }
+        } else {
+            abort();//fix cc 精简
+        }
+        for (var i = 0; i < npart; i++) {
+            for (j = 0; j < npart; j++) {
+                if (s3[i][j] > 0.0)
+                    break;
+            }
+            s3ind[i][0] = j;
+
+            for (j = npart - 1; j > 0; j--) {
+                if (s3[i][j] > 0.0)
+                    break;
+            }
+            s3ind[i][1] = j;
+            numberOfNoneZero += (s3ind[i][1] - s3ind[i][0] + 1);
+        }
+
+        var p = new_float(numberOfNoneZero);
+        var k = 0;
+        for (var i = 0; i < npart; i++)
+            for (j = s3ind[i][0]; j <= s3ind[i][1]; j++)
+                p[k++] = s3[i][j];
+
+        return p;
+    }
+
+    function stereo_demask(f) {
+        /* setup stereo demasking thresholds */
+        /* formula reverse enginerred from plot in paper */
+        var arg = freq2bark(f);
+        arg = (Math.min(arg, 15.5) / 15.5);
+
+        return Math.pow(10.0,
+            1.25 * (1 - Math.cos(Math.PI * arg)) - 2.5);
+    }
+
+    /**
+     * NOTE: the bitrate reduction from the inter-channel masking effect is low
+     * compared to the chance of getting annyoing artefacts. L3psycho_anal_vbr
+     * does not use this feature. (Robert 071216)
+     */
+    this.psymodel_init = function (gfp) {
+        var gfc = gfp.internal_flags;
+        var i;
+        var useOldS3 = true;
+        var bvl_a = 13, bvl_b = 24;
+        var snr_l_a = 0, snr_l_b = 0;
+        var snr_s_a = -8.25, snr_s_b = -4.5;
+        var bval = new_float(Encoder.CBANDS);
+        var bval_width = new_float(Encoder.CBANDS);
+        var norm = new_float(Encoder.CBANDS);
+        var sfreq = gfp.out_samplerate;
+
+        switch (gfp.experimentalZ) {
+            default:
+            case 0:
+                useOldS3 = true;
+                break;
+            case 1:
+                useOldS3 = (gfp.VBR == VbrMode.vbr_mtrh || gfp.VBR == VbrMode.vbr_mt) ? false
+                    : true;
+                break;
+            case 2:
+                useOldS3 = false;
+                break;
+            case 3:
+                bvl_a = 8;
+                snr_l_a = -1.75;
+                snr_l_b = -0.0125;
+                snr_s_a = -8.25;
+                snr_s_b = -2.25;
+                break;
+        }
+        gfc.ms_ener_ratio_old = .25;
+        gfc.blocktype_old[0] = gfc.blocktype_old[1] = Encoder.NORM_TYPE;
+        // the vbr header is long blocks
+
+        for (i = 0; i < 4; ++i) {
+            for (var j = 0; j < Encoder.CBANDS; ++j) {
+                gfc.nb_1[i][j] = 1e20;
+                gfc.nb_2[i][j] = 1e20;
+                gfc.nb_s1[i][j] = gfc.nb_s2[i][j] = 1.0;
+            }
+            for (var sb = 0; sb < Encoder.SBMAX_l; sb++) {
+                gfc.en[i].l[sb] = 1e20;
+                gfc.thm[i].l[sb] = 1e20;
+            }
+            for (var j = 0; j < 3; ++j) {
+                for (var sb = 0; sb < Encoder.SBMAX_s; sb++) {
+                    gfc.en[i].s[sb][j] = 1e20;
+                    gfc.thm[i].s[sb][j] = 1e20;
+                }
+                gfc.nsPsy.lastAttacks[i] = 0;
+            }
+            for (var j = 0; j < 9; j++)
+                gfc.nsPsy.last_en_subshort[i][j] = 10.;
+        }
+
+        /* init. for loudness approx. -jd 2001 mar 27 */
+        gfc.loudness_sq_save[0] = gfc.loudness_sq_save[1] = 0.0;
+
+        /*************************************************************************
+         * now compute the psychoacoustic model specific constants
+         ************************************************************************/
+        /* compute numlines, bo, bm, bval, bval_width, mld */
+
+        gfc.npart_l = init_numline(gfc.numlines_l, gfc.bo_l, gfc.bm_l, bval,
+            bval_width, gfc.mld_l, gfc.PSY.bo_l_weight, sfreq,
+            Encoder.BLKSIZE, gfc.scalefac_band.l, Encoder.BLKSIZE
+            / (2.0 * 576), Encoder.SBMAX_l);
+        /* compute the spreading function */
+        for (i = 0; i < gfc.npart_l; i++) {
+            var snr = snr_l_a;
+            if (bval[i] >= bvl_a) {
+                snr = snr_l_b * (bval[i] - bvl_a) / (bvl_b - bvl_a) + snr_l_a
+                    * (bvl_b - bval[i]) / (bvl_b - bvl_a);
+            }
+            norm[i] = Math.pow(10.0, snr / 10.0);
+            if (gfc.numlines_l[i] > 0) {
+                gfc.rnumlines_l[i] = 1.0 / gfc.numlines_l[i];
+            } else {
+                gfc.rnumlines_l[i] = 0;
+            }
+        }
+        gfc.s3_ll = init_s3_values(gfc.s3ind, gfc.npart_l, bval, bval_width,
+            norm, useOldS3);
+
+        /* compute long block specific values, ATH and MINVAL */
+        var j = 0;
+        for (i = 0; i < gfc.npart_l; i++) {
+            var x;
+
+            /* ATH */
+            x = Float.MAX_VALUE;
+            for (var k = 0; k < gfc.numlines_l[i]; k++, j++) {
+                var freq = sfreq * j / (1000.0 * Encoder.BLKSIZE);
+                var level;
+                /*
+                 * ATH below 100 Hz constant, not further climbing
+                 */
+                level = this.ATHformula(freq * 1000, gfp) - 20;
+                // scale to FFT units; returned value is in dB
+                level = Math.pow(10., 0.1 * level);
+                // convert from dB . energy
+                level *= gfc.numlines_l[i];
+                if (x > level)
+                    x = level;
+            }
+            gfc.ATH.cb_l[i] = x;
+
+            /*
+             * MINVAL. For low freq, the strength of the masking is limited by
+             * minval this is an ISO MPEG1 thing, dont know if it is really
+             * needed
+             */
+            /*
+             * FIXME: it does work to reduce low-freq problems in S53-Wind-Sax
+             * and lead-voice samples, but introduces some 3 kbps bit bloat too.
+             * TODO: Further refinement of the shape of this hack.
+             */
+            x = -20 + bval[i] * 20 / 10;
+            if (x > 6) {
+                x = 100;
+            }
+            if (x < -15) {
+                x = -15;
+            }
+            x -= 8.;
+            gfc.minval_l[i] = (Math.pow(10.0, x / 10.) * gfc.numlines_l[i]);
+        }
+
+        /************************************************************************
+         * do the same things for short blocks
+         ************************************************************************/
+        gfc.npart_s = init_numline(gfc.numlines_s, gfc.bo_s, gfc.bm_s, bval,
+            bval_width, gfc.mld_s, gfc.PSY.bo_s_weight, sfreq,
+            Encoder.BLKSIZE_s, gfc.scalefac_band.s, Encoder.BLKSIZE_s
+            / (2.0 * 192), Encoder.SBMAX_s);
+
+        /* SNR formula. short block is normalized by SNR. is it still right ? */
+        j = 0;
+        for (i = 0; i < gfc.npart_s; i++) {
+            var x;
+            var snr = snr_s_a;
+            if (bval[i] >= bvl_a) {
+                snr = snr_s_b * (bval[i] - bvl_a) / (bvl_b - bvl_a) + snr_s_a
+                    * (bvl_b - bval[i]) / (bvl_b - bvl_a);
+            }
+            norm[i] = Math.pow(10.0, snr / 10.0);
+
+            /* ATH */
+            x = Float.MAX_VALUE;
+            for (var k = 0; k < gfc.numlines_s[i]; k++, j++) {
+                var freq = sfreq * j / (1000.0 * Encoder.BLKSIZE_s);
+                var level;
+                /* freq = Min(.1,freq); */
+                /*
+                 * ATH below 100 Hz constant, not
+                 * further climbing
+                 */
+                level = this.ATHformula(freq * 1000, gfp) - 20;
+                // scale to FFT units; returned value is in dB
+                level = Math.pow(10., 0.1 * level);
+                // convert from dB . energy
+                level *= gfc.numlines_s[i];
+                if (x > level)
+                    x = level;
+            }
+            gfc.ATH.cb_s[i] = x;
+
+            /*
+             * MINVAL. For low freq, the strength of the masking is limited by
+             * minval this is an ISO MPEG1 thing, dont know if it is really
+             * needed
+             */
+            x = (-7.0 + bval[i] * 7.0 / 12.0);
+            if (bval[i] > 12) {
+                x *= 1 + Math.log(1 + x) * 3.1;
+            }
+            if (bval[i] < 12) {
+                x *= 1 + Math.log(1 - x) * 2.3;
+            }
+            if (x < -15) {
+                x = -15;
+            }
+            x -= 8;
+            gfc.minval_s[i] = Math.pow(10.0, x / 10)
+                * gfc.numlines_s[i];
+        }
+
+        gfc.s3_ss = init_s3_values(gfc.s3ind_s, gfc.npart_s, bval, bval_width,
+            norm, useOldS3);
+
+        init_mask_add_max_values();
+        fft.init_fft(gfc);
+
+        /* setup temporal masking */
+        gfc.decay = Math.exp(-1.0 * LOG10
+            / (temporalmask_sustain_sec * sfreq / 192.0));
+
+        {
+            var msfix;
+            msfix = NS_MSFIX;
+            if ((gfp.exp_nspsytune & 2) != 0)
+                msfix = 1.0;
+            if (Math.abs(gfp.msfix) > 0.0)
+                msfix = gfp.msfix;
+            gfp.msfix = msfix;
+
+            /*
+             * spread only from npart_l bands. Normally, we use the spreading
+             * function to convolve from npart_l down to npart_l bands
+             */
+            for (var b = 0; b < gfc.npart_l; b++)
+                if (gfc.s3ind[b][1] > gfc.npart_l - 1)
+                    gfc.s3ind[b][1] = gfc.npart_l - 1;
+        }
+
+        /*
+         * prepare for ATH auto adjustment: we want to decrease the ATH by 12 dB
+         * per second
+         */
+        var frame_duration = (576. * gfc.mode_gr / sfreq);
+        gfc.ATH.decay = Math.pow(10., -12. / 10. * frame_duration);
+        gfc.ATH.adjust = 0.01;
+        /* minimum, for leading low loudness */
+        gfc.ATH.adjustLimit = 1.0;
+        /* on lead, allow adjust up to maximum */
+
+
+        if (gfp.ATHtype != -1) {
+            /* compute equal loudness weights (eql_w) */
+            var freq;
+            var freq_inc = gfp.out_samplerate
+                / (Encoder.BLKSIZE);
+            var eql_balance = 0.0;
+            freq = 0.0;
+            for (i = 0; i < Encoder.BLKSIZE / 2; ++i) {
+                /* convert ATH dB to relative power (not dB) */
+                /* to determine eql_w */
+                freq += freq_inc;
+                gfc.ATH.eql_w[i] = 1. / Math.pow(10, this.ATHformula(freq, gfp) / 10);
+                eql_balance += gfc.ATH.eql_w[i];
+            }
+            eql_balance = 1.0 / eql_balance;
+            for (i = Encoder.BLKSIZE / 2; --i >= 0;) { /* scale weights */
+                gfc.ATH.eql_w[i] *= eql_balance;
+            }
+        }
+        {
+            for (var b = j = 0; b < gfc.npart_s; ++b) {
+                for (i = 0; i < gfc.numlines_s[b]; ++i) {
+                    ++j;
+                }
+            }
+            for (var b = j = 0; b < gfc.npart_l; ++b) {
+                for (i = 0; i < gfc.numlines_l[b]; ++i) {
+                    ++j;
+                }
+            }
+        }
+        j = 0;
+        for (i = 0; i < gfc.npart_l; i++) {
+            var freq = sfreq * (j + gfc.numlines_l[i] / 2) / (1.0 * Encoder.BLKSIZE);
+            gfc.mld_cb_l[i] = stereo_demask(freq);
+            j += gfc.numlines_l[i];
+        }
+        for (; i < Encoder.CBANDS; ++i) {
+            gfc.mld_cb_l[i] = 1;
+        }
+        j = 0;
+        for (i = 0; i < gfc.npart_s; i++) {
+            var freq = sfreq * (j + gfc.numlines_s[i] / 2) / (1.0 * Encoder.BLKSIZE_s);
+            gfc.mld_cb_s[i] = stereo_demask(freq);
+            j += gfc.numlines_s[i];
+        }
+        for (; i < Encoder.CBANDS; ++i) {
+            gfc.mld_cb_s[i] = 1;
+        }
+        return 0;
+    }
+
+    /**
+     * Those ATH formulas are returning their minimum value for input = -1
+     */
+    function ATHformula_GB(f, value) {
+        /**
+         * <PRE>
+         *  from Painter & Spanias
+         *           modified by Gabriel Bouvigne to better fit the reality
+         *           ath =    3.640 * pow(f,-0.8)
+         *           - 6.800 * exp(-0.6*pow(f-3.4,2.0))
+         *           + 6.000 * exp(-0.15*pow(f-8.7,2.0))
+         *           + 0.6* 0.001 * pow(f,4.0);
+         *
+         *
+         *           In the past LAME was using the Painter &Spanias formula.
+         *           But we had some recurrent problems with HF content.
+         *           We measured real ATH values, and found the older formula
+         *           to be inaccurate in the higher part. So we made this new
+         *           formula and this solved most of HF problematic test cases.
+         *           The tradeoff is that in VBR mode it increases a lot the
+         *           bitrate.
+         * </PRE>
+         */
+
+        /*
+         * This curve can be adjusted according to the VBR scale: it adjusts
+         * from something close to Painter & Spanias on V9 up to Bouvigne's
+         * formula for V0. This way the VBR bitrate is more balanced according
+         * to the -V value.
+         */
+
+        // the following Hack allows to ask for the lowest value
+        if (f < -.3)
+            f = 3410;
+
+        // convert to khz
+        f /= 1000;
+        f = Math.max(0.1, f);
+        var ath = 3.640 * Math.pow(f, -0.8) - 6.800
+            * Math.exp(-0.6 * Math.pow(f - 3.4, 2.0)) + 6.000
+            * Math.exp(-0.15 * Math.pow(f - 8.7, 2.0))
+            + (0.6 + 0.04 * value) * 0.001 * Math.pow(f, 4.0);
+        return ath;
+    }
+
+    this.ATHformula = function (f, gfp) {
+        var ath;
+        switch (gfp.ATHtype) {
+            case 0:
+                ath = ATHformula_GB(f, 9);
+                break;
+            case 1:
+                // over sensitive, should probably be removed
+                ath = ATHformula_GB(f, -1);
+                break;
+            case 2:
+                ath = ATHformula_GB(f, 0);
+                break;
+            case 3:
+                // modification of GB formula by Roel
+                ath = ATHformula_GB(f, 1) + 6;
+                break;
+            case 4:
+                ath = ATHformula_GB(f, gfp.ATHcurve);
+                break;
+            default:
+                ath = ATHformula_GB(f, 0);
+                break;
+        }
+        return ath;
+    }
+
+}
+
+
+
+function Lame() {
+    var self = this;
+    var LAME_MAXALBUMART = (128 * 1024);
+
+    Lame.V9 = 410;
+    Lame.V8 = 420;
+    Lame.V7 = 430;
+    Lame.V6 = 440;
+    Lame.V5 = 450;
+    Lame.V4 = 460;
+    Lame.V3 = 470;
+    Lame.V2 = 480;
+    Lame.V1 = 490;
+    Lame.V0 = 500;
+
+    /* still there for compatibility */
+
+    Lame.R3MIX = 1000;
+    Lame.STANDARD = 1001;
+    Lame.EXTREME = 1002;
+    Lame.INSANE = 1003;
+    Lame.STANDARD_FAST = 1004;
+    Lame.EXTREME_FAST = 1005;
+    Lame.MEDIUM = 1006;
+    Lame.MEDIUM_FAST = 1007;
+
+    /**
+     * maximum size of mp3buffer needed if you encode at most 1152 samples for
+     * each call to lame_encode_buffer. see lame_encode_buffer() below
+     * (LAME_MAXMP3BUFFER is now obsolete)
+     */
+    var LAME_MAXMP3BUFFER = (16384 + LAME_MAXALBUMART);
+    Lame.LAME_MAXMP3BUFFER = LAME_MAXMP3BUFFER;
+
+    var ga;
+    var bs;
+    var p;
+    var qupvt;
+    var qu;
+    var psy = new PsyModel();
+    var vbr;
+    var ver;
+    var id3;
+    var mpglib;
+    this.enc = new Encoder();
+
+    this.setModules = function (_ga, _bs, _p, _qupvt, _qu, _vbr, _ver, _id3, _mpglib) {
+        ga = _ga;
+        bs = _bs;
+        p = _p;
+        qupvt = _qupvt;
+        qu = _qu;
+        vbr = _vbr;
+        ver = _ver;
+        id3 = _id3;
+        mpglib = _mpglib;
+        this.enc.setModules(bs, psy, qupvt, vbr);
+    }
+
+    /**
+     * PSY Model related stuff
+     */
+    function PSY() {
+        /**
+         * The dbQ stuff.
+         */
+        this.mask_adjust = 0.;
+        /**
+         * The dbQ stuff.
+         */
+        this.mask_adjust_short = 0.;
+        /* at transition from one scalefactor band to next */
+        /**
+         * Band weight long scalefactor bands.
+         */
+        this.bo_l_weight = new_float(Encoder.SBMAX_l);
+        /**
+         * Band weight short scalefactor bands.
+         */
+        this.bo_s_weight = new_float(Encoder.SBMAX_s);
+    }
+
+    function LowPassHighPass() {
+        this.lowerlimit = 0.;
+    }
+
+    function BandPass(bitrate, lPass) {
+        this.lowpass = lPass;
+    }
+
+    var LAME_ID = 0xFFF88E3B;
+
+    function lame_init_old(gfp) {
+        var gfc;
+
+        gfp.class_id = LAME_ID;
+
+        gfc = gfp.internal_flags = new LameInternalFlags();
+
+        /* Global flags. set defaults here for non-zero values */
+        /* see lame.h for description */
+        /*
+         * set integer values to -1 to mean that LAME will compute the best
+         * value, UNLESS the calling program as set it (and the value is no
+         * longer -1)
+         */
+
+        gfp.mode = MPEGMode.NOT_SET;
+        gfp.original = 1;
+        gfp.in_samplerate = 44100;
+        gfp.num_channels = 2;
+        gfp.num_samples = -1;
+
+        gfp.bWriteVbrTag = true;
+        gfp.quality = -1;
+        gfp.short_blocks = null;
+        gfc.subblock_gain = -1;
+
+        gfp.lowpassfreq = 0;
+        gfp.highpassfreq = 0;
+        gfp.lowpasswidth = -1;
+        gfp.highpasswidth = -1;
+
+        gfp.VBR = VbrMode.vbr_off;
+        gfp.VBR_q = 4;
+        gfp.ATHcurve = -1;
+        gfp.VBR_mean_bitrate_kbps = 128;
+        gfp.VBR_min_bitrate_kbps = 0;
+        gfp.VBR_max_bitrate_kbps = 0;
+        gfp.VBR_hard_min = 0;
+        gfc.VBR_min_bitrate = 1;
+        /* not 0 ????? */
+        gfc.VBR_max_bitrate = 13;
+        /* not 14 ????? */
+
+        gfp.quant_comp = -1;
+        gfp.quant_comp_short = -1;
+
+        gfp.msfix = -1;
+
+        gfc.resample_ratio = 1;
+
+        gfc.OldValue[0] = 180;
+        gfc.OldValue[1] = 180;
+        gfc.CurrentStep[0] = 4;
+        gfc.CurrentStep[1] = 4;
+        gfc.masking_lower = 1;
+        gfc.nsPsy.attackthre = -1;
+        gfc.nsPsy.attackthre_s = -1;
+
+        gfp.scale = -1;
+
+        gfp.athaa_type = -1;
+        gfp.ATHtype = -1;
+        /* default = -1 = set in lame_init_params */
+        gfp.athaa_loudapprox = -1;
+        /* 1 = flat loudness approx. (total energy) */
+        /* 2 = equal loudness curve */
+        gfp.athaa_sensitivity = 0.0;
+        /* no offset */
+        gfp.useTemporal = null;
+        gfp.interChRatio = -1;
+
+        /*
+         * The reason for int mf_samples_to_encode = ENCDELAY + POSTDELAY;
+         * ENCDELAY = internal encoder delay. And then we have to add
+         * POSTDELAY=288 because of the 50% MDCT overlap. A 576 MDCT granule
+         * decodes to 1152 samples. To synthesize the 576 samples centered under
+         * this granule we need the previous granule for the first 288 samples
+         * (no problem), and the next granule for the next 288 samples (not
+         * possible if this is last granule). So we need to pad with 288 samples
+         * to make sure we can encode the 576 samples we are interested in.
+         */
+        gfc.mf_samples_to_encode = Encoder.ENCDELAY + Encoder.POSTDELAY;
+        gfp.encoder_padding = 0;
+        gfc.mf_size = Encoder.ENCDELAY - Encoder.MDCTDELAY;
+        /*
+         * we pad input with this many 0's
+         */
+
+        gfp.findReplayGain = false;
+        gfp.decode_on_the_fly = false;
+
+        gfc.decode_on_the_fly = false;
+        gfc.findReplayGain = false;
+        gfc.findPeakSample = false;
+
+        gfc.RadioGain = 0;
+        gfc.AudiophileGain = 0;
+        gfc.noclipGainChange = 0;
+        gfc.noclipScale = -1.0;
+
+        gfp.preset = 0;
+
+        gfp.write_id3tag_automatic = true;
+        return 0;
+    }
+
+    this.lame_init = function () {
+        var gfp = new LameGlobalFlags();
+
+        var ret = lame_init_old(gfp);
+        if (ret != 0) {
+            return null;
+        }
+
+        gfp.lame_allocated_gfp = 1;
+        return gfp;
+    }
+
+    function filter_coef(x) {
+        if (x > 1.0)
+            return 0.0;
+        if (x <= 0.0)
+            return 1.0;
+
+        return Math.cos(Math.PI / 2 * x);
+    }
+
+    this.nearestBitrateFullIndex = function (bitrate) {
+        /* borrowed from DM abr presets */
+
+        var full_bitrate_table = [8, 16, 24, 32, 40, 48, 56, 64, 80,
+            96, 112, 128, 160, 192, 224, 256, 320];
+
+        var lower_range = 0, lower_range_kbps = 0, upper_range = 0, upper_range_kbps = 0;
+
+        /* We assume specified bitrate will be 320kbps */
+        upper_range_kbps = full_bitrate_table[16];
+        upper_range = 16;
+        lower_range_kbps = full_bitrate_table[16];
+        lower_range = 16;
+
+        /*
+         * Determine which significant bitrates the value specified falls
+         * between, if loop ends without breaking then we were correct above
+         * that the value was 320
+         */
+        for (var b = 0; b < 16; b++) {
+            if ((Math.max(bitrate, full_bitrate_table[b + 1])) != bitrate) {
+                upper_range_kbps = full_bitrate_table[b + 1];
+                upper_range = b + 1;
+                lower_range_kbps = full_bitrate_table[b];
+                lower_range = (b);
+                break;
+                /* We found upper range */
+            }
+        }
+
+        /* Determine which range the value specified is closer to */
+        if ((upper_range_kbps - bitrate) > (bitrate - lower_range_kbps)) {
+            return lower_range;
+        }
+        return upper_range;
+    }
+
+    //fix cc 精简
+
+    /**
+     * convert samp freq in Hz to index
+     */
+    function SmpFrqIndex(sample_freq, gpf) {
+        switch (sample_freq) {
+            case 44100:
+                gpf.version = 1;
+                return 0;
+            case 48000:
+                gpf.version = 1;
+                return 1;
+            case 32000:
+                gpf.version = 1;
+                return 2;
+            case 22050:
+                gpf.version = 0;
+                return 0;
+            case 24000:
+                gpf.version = 0;
+                return 1;
+            case 16000:
+                gpf.version = 0;
+                return 2;
+            case 11025:
+                gpf.version = 0;
+                return 0;
+            case 12000:
+                gpf.version = 0;
+                return 1;
+            case 8000:
+                gpf.version = 0;
+                return 2;
+            default:
+                gpf.version = 0;
+                return -1;
+        }
+    }
+
+    /**
+     * @param bRate
+     *            legal rates from 8 to 320
+     */
+    function FindNearestBitrate(bRate, version, samplerate) {
+        /* MPEG-1 or MPEG-2 LSF */
+        if (samplerate < 16000)
+            version = 2;
+
+        var bitrate = Tables.bitrate_table[version][1];
+
+        for (var i = 2; i <= 14; i++) {
+            if (Tables.bitrate_table[version][i] > 0) {
+                if (Math.abs(Tables.bitrate_table[version][i] - bRate) < Math
+                        .abs(bitrate - bRate))
+                    bitrate = Tables.bitrate_table[version][i];
+            }
+        }
+        return bitrate;
+    }
+
+    /**
+     * @param bRate
+     *            legal rates from 32 to 448 kbps
+     * @param version
+     *            MPEG-1 or MPEG-2/2.5 LSF
+     */
+    function BitrateIndex(bRate, version, samplerate) {
+        /* convert bitrate in kbps to index */
+        if (samplerate < 16000)
+            version = 2;
+        for (var i = 0; i <= 14; i++) {
+            if (Tables.bitrate_table[version][i] > 0) {
+                if (Tables.bitrate_table[version][i] == bRate) {
+                    return i;
+                }
+            }
+        }
+        return -1;
+    }
+
+    function optimum_bandwidth(lh, bitrate) {
+        /**
+         * <PRE>
+         *  Input:
+         *      bitrate     total bitrate in kbps
+         *
+         *   Output:
+         *      lowerlimit: best lowpass frequency limit for input filter in Hz
+         *      upperlimit: best highpass frequency limit for input filter in Hz
+         * </PRE>
+         */
+        var freq_map = [new BandPass(8, 2000),
+            new BandPass(16, 3700), new BandPass(24, 3900),
+            new BandPass(32, 5500), new BandPass(40, 7000),
+            new BandPass(48, 7500), new BandPass(56, 10000),
+            new BandPass(64, 11000), new BandPass(80, 13500),
+            new BandPass(96, 15100), new BandPass(112, 15600),
+            new BandPass(128, 17000), new BandPass(160, 17500),
+            new BandPass(192, 18600), new BandPass(224, 19400),
+            new BandPass(256, 19700), new BandPass(320, 20500)];
+
+        var table_index = self.nearestBitrateFullIndex(bitrate);
+        lh.lowerlimit = freq_map[table_index].lowpass;
+    }
+
+    function lame_init_params_ppflt(gfp) {
+        var gfc = gfp.internal_flags;
+        /***************************************************************/
+        /* compute info needed for polyphase filter (filter type==0, default) */
+        /***************************************************************/
+
+        var lowpass_band = 32;
+        var highpass_band = -1;
+
+        if (gfc.lowpass1 > 0) {
+            var minband = 999;
+            for (var band = 0; band <= 31; band++) {
+                var freq = (band / 31.0);
+                /* this band and above will be zeroed: */
+                if (freq >= gfc.lowpass2) {
+                    lowpass_band = Math.min(lowpass_band, band);
+                }
+                if (gfc.lowpass1 < freq && freq < gfc.lowpass2) {
+                    minband = Math.min(minband, band);
+                }
+            }
+
+            /*
+             * compute the *actual* transition band implemented by the polyphase
+             * filter
+             */
+            if (minband == 999) {
+                gfc.lowpass1 = (lowpass_band - .75) / 31.0;
+            } else {
+                gfc.lowpass1 = (minband - .75) / 31.0;
+            }
+            gfc.lowpass2 = lowpass_band / 31.0;
+        }
+
+        /*
+         * make sure highpass filter is within 90% of what the effective
+         * highpass frequency will be
+         */
+        if (gfc.highpass2 > 0) {
+            abort();//fix cc 精简
+        }
+
+        if (gfc.highpass2 > 0) {
+            abort();//fix cc 精简
+        }
+
+        for (var band = 0; band < 32; band++) {
+            var fc1, fc2;
+            var freq = band / 31.0;
+            if (gfc.highpass2 > gfc.highpass1) {
+                abort();//fix cc 精简
+            } else {
+                fc1 = 1.0;
+            }
+            if (gfc.lowpass2 > gfc.lowpass1) {
+                fc2 = filter_coef((freq - gfc.lowpass1)
+                    / (gfc.lowpass2 - gfc.lowpass1 + 1e-20));
+            } else {
+                fc2 = 1.0;
+            }
+            gfc.amp_filter[band] = (fc1 * fc2);
+        }
+    }
+
+    function lame_init_qval(gfp) {
+        var gfc = gfp.internal_flags;
+
+        switch (gfp.quality) {
+            default:
+            case 9: /* no psymodel, no noise shaping */
+                gfc.psymodel = 0;
+                gfc.noise_shaping = 0;
+                gfc.noise_shaping_amp = 0;
+                gfc.noise_shaping_stop = 0;
+                gfc.use_best_huffman = 0;
+                gfc.full_outer_loop = 0;
+                break;
+
+            case 8:
+                gfp.quality = 7;
+            //$FALL-THROUGH$
+            case 7:
+                /*
+                 * use psymodel (for short block and m/s switching), but no noise
+                 * shapping
+                 */
+                gfc.psymodel = 1;
+                gfc.noise_shaping = 0;
+                gfc.noise_shaping_amp = 0;
+                gfc.noise_shaping_stop = 0;
+                gfc.use_best_huffman = 0;
+                gfc.full_outer_loop = 0;
+                break;
+
+            case 6:
+                gfc.psymodel = 1;
+                if (gfc.noise_shaping == 0)
+                    gfc.noise_shaping = 1;
+                gfc.noise_shaping_amp = 0;
+                gfc.noise_shaping_stop = 0;
+                if (gfc.subblock_gain == -1)
+                    gfc.subblock_gain = 1;
+                gfc.use_best_huffman = 0;
+                gfc.full_outer_loop = 0;
+                break;
+
+            case 5:
+                gfc.psymodel = 1;
+                if (gfc.noise_shaping == 0)
+                    gfc.noise_shaping = 1;
+                gfc.noise_shaping_amp = 0;
+                gfc.noise_shaping_stop = 0;
+                if (gfc.subblock_gain == -1)
+                    gfc.subblock_gain = 1;
+                gfc.use_best_huffman = 0;
+                gfc.full_outer_loop = 0;
+                break;
+
+            case 4:
+                gfc.psymodel = 1;
+                if (gfc.noise_shaping == 0)
+                    gfc.noise_shaping = 1;
+                gfc.noise_shaping_amp = 0;
+                gfc.noise_shaping_stop = 0;
+                if (gfc.subblock_gain == -1)
+                    gfc.subblock_gain = 1;
+                gfc.use_best_huffman = 1;
+                gfc.full_outer_loop = 0;
+                break;
+
+            case 3:
+                gfc.psymodel = 1;
+                if (gfc.noise_shaping == 0)
+                    gfc.noise_shaping = 1;
+                gfc.noise_shaping_amp = 1;
+                gfc.noise_shaping_stop = 1;
+                if (gfc.subblock_gain == -1)
+                    gfc.subblock_gain = 1;
+                gfc.use_best_huffman = 1;
+                gfc.full_outer_loop = 0;
+                break;
+
+            case 2:
+                gfc.psymodel = 1;
+                if (gfc.noise_shaping == 0)
+                    gfc.noise_shaping = 1;
+                if (gfc.substep_shaping == 0)
+                    gfc.substep_shaping = 2;
+                gfc.noise_shaping_amp = 1;
+                gfc.noise_shaping_stop = 1;
+                if (gfc.subblock_gain == -1)
+                    gfc.subblock_gain = 1;
+                gfc.use_best_huffman = 1;
+                /* inner loop */
+                gfc.full_outer_loop = 0;
+                break;
+
+            case 1:
+                gfc.psymodel = 1;
+                if (gfc.noise_shaping == 0)
+                    gfc.noise_shaping = 1;
+                if (gfc.substep_shaping == 0)
+                    gfc.substep_shaping = 2;
+                gfc.noise_shaping_amp = 2;
+                gfc.noise_shaping_stop = 1;
+                if (gfc.subblock_gain == -1)
+                    gfc.subblock_gain = 1;
+                gfc.use_best_huffman = 1;
+                gfc.full_outer_loop = 0;
+                break;
+
+            case 0:
+                gfc.psymodel = 1;
+                if (gfc.noise_shaping == 0)
+                    gfc.noise_shaping = 1;
+                if (gfc.substep_shaping == 0)
+                    gfc.substep_shaping = 2;
+                gfc.noise_shaping_amp = 2;
+                gfc.noise_shaping_stop = 1;
+                if (gfc.subblock_gain == -1)
+                    gfc.subblock_gain = 1;
+                gfc.use_best_huffman = 1;
+                /*
+                 * type 2 disabled because of it slowness, in favor of full outer
+                 * loop search
+                 */
+                gfc.full_outer_loop = 0;
+                /*
+                 * full outer loop search disabled because of audible distortions it
+                 * may generate rh 060629
+                 */
+                break;
+        }
+
+    }
+
+    function lame_init_bitstream(gfp) {
+        var gfc = gfp.internal_flags;
+        gfp.frameNum = 0;
+
+        if (gfp.write_id3tag_automatic) {
+            id3.id3tag_write_v2(gfp);
+        }
+        /* initialize histogram data optionally used by frontend */
+
+        gfc.bitrate_stereoMode_Hist = new_int_n([16, 4 + 1]);
+        gfc.bitrate_blockType_Hist = new_int_n([16, 4 + 1 + 1]);
+
+        gfc.PeakSample = 0.0;
+
+        /* Write initial VBR Header to bitstream and init VBR data */
+        if (gfp.bWriteVbrTag)
+            vbr.InitVbrTag(gfp);
+    }
+
+    /********************************************************************
+     * initialize internal params based on data in gf (globalflags struct filled
+     * in by calling program)
+     *
+     * OUTLINE:
+     *
+     * We first have some complex code to determine bitrate, output samplerate
+     * and mode. It is complicated by the fact that we allow the user to set
+     * some or all of these parameters, and need to determine best possible
+     * values for the rest of them:
+     *
+     * 1. set some CPU related flags 2. check if we are mono.mono, stereo.mono
+     * or stereo.stereo 3. compute bitrate and output samplerate: user may have
+     * set compression ratio user may have set a bitrate user may have set a
+     * output samplerate 4. set some options which depend on output samplerate
+     * 5. compute the actual compression ratio 6. set mode based on compression
+     * ratio
+     *
+     * The remaining code is much simpler - it just sets options based on the
+     * mode & compression ratio:
+     *
+     * set allow_diff_short based on mode select lowpass filter based on
+     * compression ratio & mode set the bitrate index, and min/max bitrates for
+     * VBR modes disable VBR tag if it is not appropriate initialize the
+     * bitstream initialize scalefac_band data set sideinfo_len (based on
+     * channels, CRC, out_samplerate) write an id3v2 tag into the bitstream
+     * write VBR tag into the bitstream set mpeg1/2 flag estimate the number of
+     * frames (based on a lot of data)
+     *
+     * now we set more flags: nspsytune: see code VBR modes see code CBR/ABR see
+     * code
+     *
+     * Finally, we set the algorithm flags based on the gfp.quality value
+     * lame_init_qval(gfp);
+     *
+     ********************************************************************/
+    this.lame_init_params = function (gfp) {
+        var gfc = gfp.internal_flags;
+
+        gfc.Class_ID = 0;
+        if (gfc.ATH == null)
+            gfc.ATH = new ATH();
+        if (gfc.PSY == null)
+            gfc.PSY = new PSY();
+        if (gfc.rgdata == null)
+            gfc.rgdata = new ReplayGain();
+
+        gfc.channels_in = gfp.num_channels;
+        if (gfc.channels_in == 1)
+            gfp.mode = MPEGMode.MONO;
+        gfc.channels_out = (gfp.mode == MPEGMode.MONO) ? 1 : 2;
+        gfc.mode_ext = Encoder.MPG_MD_MS_LR;
+        if (gfp.mode == MPEGMode.MONO)
+            gfp.force_ms = false;
+        /*
+         * don't allow forced mid/side stereo for mono output
+         */
+
+        if (gfp.VBR == VbrMode.vbr_off && gfp.VBR_mean_bitrate_kbps != 128
+            && gfp.brate == 0)
+            gfp.brate = gfp.VBR_mean_bitrate_kbps;
+
+        if (gfp.VBR == VbrMode.vbr_off || gfp.VBR == VbrMode.vbr_mtrh
+            || gfp.VBR == VbrMode.vbr_mt) {
+            /* these modes can handle free format condition */
+        } else {
+            gfp.free_format = false;
+            /* mode can't be mixed with free format */
+        }
+
+        if (gfp.VBR == VbrMode.vbr_off && gfp.brate == 0) {
+            abort();//fix cc 精简
+        }
+
+        /* find bitrate if user specify a compression ratio */
+        if (gfp.VBR == VbrMode.vbr_off && gfp.compression_ratio > 0) {
+            abort();//fix cc 精简
+        }
+
+        if (gfp.out_samplerate != 0) {
+            if (gfp.out_samplerate < 16000) {
+                gfp.VBR_mean_bitrate_kbps = Math.max(gfp.VBR_mean_bitrate_kbps,
+                    8);
+                gfp.VBR_mean_bitrate_kbps = Math.min(gfp.VBR_mean_bitrate_kbps,
+                    64);
+            } else if (gfp.out_samplerate < 32000) {
+                gfp.VBR_mean_bitrate_kbps = Math.max(gfp.VBR_mean_bitrate_kbps,
+                    8);
+                gfp.VBR_mean_bitrate_kbps = Math.min(gfp.VBR_mean_bitrate_kbps,
+                    160);
+            } else {
+                gfp.VBR_mean_bitrate_kbps = Math.max(gfp.VBR_mean_bitrate_kbps,
+                    32);
+                gfp.VBR_mean_bitrate_kbps = Math.min(gfp.VBR_mean_bitrate_kbps,
+                    320);
+            }
+        }
+
+        /****************************************************************/
+        /* if a filter has not been enabled, see if we should add one: */
+        /****************************************************************/
+        if (gfp.lowpassfreq == 0) {
+            var lowpass = 16000.;
+
+            switch (gfp.VBR) {
+                case VbrMode.vbr_off:
+                {
+                    var lh = new LowPassHighPass();
+                    optimum_bandwidth(lh, gfp.brate);
+                    lowpass = lh.lowerlimit;
+                    break;
+                }
+                case VbrMode.vbr_abr:
+                {
+                    var lh = new LowPassHighPass();
+                    optimum_bandwidth(lh, gfp.VBR_mean_bitrate_kbps);
+                    lowpass = lh.lowerlimit;
+                    break;
+                }
+                case VbrMode.vbr_rh:
+                {
+                    abort();//fix cc 精简
+                }
+                default:
+                {
+                    abort();//fix cc 精简
+                }
+            }
+            if (gfp.mode == MPEGMode.MONO
+                && (gfp.VBR == VbrMode.vbr_off || gfp.VBR == VbrMode.vbr_abr))
+                lowpass *= 1.5;
+
+            gfp.lowpassfreq = lowpass | 0;
+        }
+
+        if (gfp.out_samplerate == 0) {
+            abort();//fix cc 精简
+        }
+
+        gfp.lowpassfreq = Math.min(20500, gfp.lowpassfreq);
+        gfp.lowpassfreq = Math.min(gfp.out_samplerate / 2, gfp.lowpassfreq);
+
+        if (gfp.VBR == VbrMode.vbr_off) {
+            gfp.compression_ratio = gfp.out_samplerate * 16 * gfc.channels_out
+                / (1.e3 * gfp.brate);
+        }
+        if (gfp.VBR == VbrMode.vbr_abr) {
+            abort();//fix cc 精简
+        }
+
+        /*
+         * do not compute ReplayGain values and do not find the peak sample if
+         * we can't store them
+         */
+        if (!gfp.bWriteVbrTag) {
+            gfp.findReplayGain = false;
+            gfp.decode_on_the_fly = false;
+            gfc.findPeakSample = false;
+        }
+        gfc.findReplayGain = gfp.findReplayGain;
+        gfc.decode_on_the_fly = gfp.decode_on_the_fly;
+
+        if (gfc.decode_on_the_fly)
+            gfc.findPeakSample = true;
+
+        if (gfc.findReplayGain) {
+            abort();//fix cc 精简
+        }
+
+        if (gfc.decode_on_the_fly && !gfp.decode_only) {
+            abort();//fix cc 精简
+        }
+
+        gfc.mode_gr = gfp.out_samplerate <= 24000 ? 1 : 2;
+        /*
+         * Number of granules per frame
+         */
+        gfp.framesize = 576 * gfc.mode_gr;
+        gfp.encoder_delay = Encoder.ENCDELAY;
+
+        gfc.resample_ratio = gfp.in_samplerate / gfp.out_samplerate;
+
+        /**
+         * <PRE>
+         *  sample freq       bitrate     compression ratio
+         *     [kHz]      [kbps/channel]   for 16 bit input
+         *     44.1            56               12.6
+         *     44.1            64               11.025
+         *     44.1            80                8.82
+         *     22.05           24               14.7
+         *     22.05           32               11.025
+         *     22.05           40                8.82
+         *     16              16               16.0
+         *     16              24               10.667
+         * </PRE>
+         */
+        /**
+         * <PRE>
+         *  For VBR, take a guess at the compression_ratio.
+         *  For example:
+         *
+         *    VBR_q    compression     like
+         *     -        4.4         320 kbps/44 kHz
+         *   0...1      5.5         256 kbps/44 kHz
+         *     2        7.3         192 kbps/44 kHz
+         *     4        8.8         160 kbps/44 kHz
+         *     6       11           128 kbps/44 kHz
+         *     9       14.7          96 kbps
+         *
+         *  for lower bitrates, downsample with --resample
+         * </PRE>
+         */
+        switch (gfp.VBR) {
+            case VbrMode.vbr_mt:
+            case VbrMode.vbr_rh:
+            case VbrMode.vbr_mtrh:
+            {
+                /* numbers are a bit strange, but they determine the lowpass value */
+                var cmp = [5.7, 6.5, 7.3, 8.2, 10, 11.9, 13, 14,
+                    15, 16.5];
+                gfp.compression_ratio = cmp[gfp.VBR_q];
+            }
+                break;
+            case VbrMode.vbr_abr:
+                gfp.compression_ratio = gfp.out_samplerate * 16 * gfc.channels_out
+                    / (1.e3 * gfp.VBR_mean_bitrate_kbps);
+                break;
+            default:
+                gfp.compression_ratio = gfp.out_samplerate * 16 * gfc.channels_out
+                    / (1.e3 * gfp.brate);
+                break;
+        }
+
+        /*
+         * mode = -1 (not set by user) or mode = MONO (because of only 1 input
+         * channel). If mode has not been set, then select J-STEREO
+         */
+        if (gfp.mode == MPEGMode.NOT_SET) {
+            gfp.mode = MPEGMode.JOINT_STEREO;
+        }
+
+        /* apply user driven high pass filter */
+        if (gfp.highpassfreq > 0) {
+            abort();//fix cc 精简
+        } else {
+            gfc.highpass1 = 0;
+            gfc.highpass2 = 0;
+        }
+        /* apply user driven low pass filter */
+        if (gfp.lowpassfreq > 0) {
+            gfc.lowpass2 = 2. * gfp.lowpassfreq;
+            if (gfp.lowpasswidth >= 0) {
+                abort();//fix cc 精简
+            } else { /* 0% below on default */
+                gfc.lowpass1 = (1 - 0.00) * 2. * gfp.lowpassfreq;
+            }
+            gfc.lowpass1 /= gfp.out_samplerate;
+            gfc.lowpass2 /= gfp.out_samplerate;
+        } else {
+            abort();//fix cc 精简
+        }
+
+        /**********************************************************************/
+        /* compute info needed for polyphase filter (filter type==0, default) */
+        /**********************************************************************/
+        lame_init_params_ppflt(gfp);
+        /*******************************************************
+         * samplerate and bitrate index
+         *******************************************************/
+        gfc.samplerate_index = SmpFrqIndex(gfp.out_samplerate, gfp);
+        if (gfc.samplerate_index < 0) {
+            abort();//fix cc 精简
+        }
+
+        if (gfp.VBR == VbrMode.vbr_off) {
+            if (gfp.free_format) {
+                gfc.bitrate_index = 0;
+            } else {
+                gfp.brate = FindNearestBitrate(gfp.brate, gfp.version,
+                    gfp.out_samplerate);
+                gfc.bitrate_index = BitrateIndex(gfp.brate, gfp.version,
+                    gfp.out_samplerate);
+                if (gfc.bitrate_index <= 0) {
+                    abort();//fix cc 精简
+                }
+            }
+        } else {
+            gfc.bitrate_index = 1;
+        }
+
+        /* for CBR, we will write an "info" tag. */
+
+        if (gfp.analysis)
+            gfp.bWriteVbrTag = false;
+
+        /* some file options not allowed if output is: not specified or stdout */
+        if (gfc.pinfo != null)
+            gfp.bWriteVbrTag = false;
+        /* disable Xing VBR tag */
+
+        bs.init_bit_stream_w(gfc);
+
+        var j = gfc.samplerate_index + (3 * gfp.version) + 6
+            * (gfp.out_samplerate < 16000 ? 1 : 0);
+        for (var i = 0; i < Encoder.SBMAX_l + 1; i++)
+            gfc.scalefac_band.l[i] = qupvt.sfBandIndex[j].l[i];
+
+        for (var i = 0; i < Encoder.PSFB21 + 1; i++) {
+            var size = (gfc.scalefac_band.l[22] - gfc.scalefac_band.l[21])
+                / Encoder.PSFB21;
+            var start = gfc.scalefac_band.l[21] + i * size;
+            gfc.scalefac_band.psfb21[i] = start;
+        }
+        gfc.scalefac_band.psfb21[Encoder.PSFB21] = 576;
+
+        for (var i = 0; i < Encoder.SBMAX_s + 1; i++)
+            gfc.scalefac_band.s[i] = qupvt.sfBandIndex[j].s[i];
+
+        for (var i = 0; i < Encoder.PSFB12 + 1; i++) {
+            var size = (gfc.scalefac_band.s[13] - gfc.scalefac_band.s[12])
+                / Encoder.PSFB12;
+            var start = gfc.scalefac_band.s[12] + i * size;
+            gfc.scalefac_band.psfb12[i] = start;
+        }
+        gfc.scalefac_band.psfb12[Encoder.PSFB12] = 192;
+        /* determine the mean bitrate for main data */
+        if (gfp.version == 1) /* MPEG 1 */
+            gfc.sideinfo_len = (gfc.channels_out == 1) ? 4 + 17 : 4 + 32;
+        else
+        /* MPEG 2 */
+            gfc.sideinfo_len = (gfc.channels_out == 1) ? 4 + 9 : 4 + 17;
+
+        if (gfp.error_protection)
+            gfc.sideinfo_len += 2;
+
+        lame_init_bitstream(gfp);
+
+        gfc.Class_ID = LAME_ID;
+
+        {
+            var k;
+
+            for (k = 0; k < 19; k++)
+                gfc.nsPsy.pefirbuf[k] = 700 * gfc.mode_gr * gfc.channels_out;
+
+            if (gfp.ATHtype == -1)
+                gfp.ATHtype = 4;
+        }
+
+        switch (gfp.VBR) {
+
+            case VbrMode.vbr_mt:
+                gfp.VBR = VbrMode.vbr_mtrh;
+            //$FALL-THROUGH$
+            case VbrMode.vbr_mtrh:
+            {
+                if (gfp.useTemporal == null) {
+                    gfp.useTemporal = false;
+                    /* off by default for this VBR mode */
+                }
+
+                p.apply_preset(gfp, 500 - (gfp.VBR_q * 10), 0);
+                /**
+                 * <PRE>
+                 *   The newer VBR code supports only a limited
+                 *     subset of quality levels:
+                 *     9-5=5 are the same, uses x^3/4 quantization
+                 *   4-0=0 are the same  5 plus best huffman divide code
+                 * </PRE>
+                 */
+                if (gfp.quality < 0)
+                    gfp.quality = LAME_DEFAULT_QUALITY;
+                if (gfp.quality < 5)
+                    gfp.quality = 0;
+                if (gfp.quality > 5)
+                    gfp.quality = 5;
+
+                gfc.PSY.mask_adjust = gfp.maskingadjust;
+                gfc.PSY.mask_adjust_short = gfp.maskingadjust_short;
+
+                /*
+                 * sfb21 extra only with MPEG-1 at higher sampling rates
+                 */
+                if (gfp.experimentalY)
+                    gfc.sfb21_extra = false;
+                else
+                    gfc.sfb21_extra = (gfp.out_samplerate > 44000);
+
+                gfc.iteration_loop = new VBRNewIterationLoop(qu);
+                break;
+
+            }
+            case VbrMode.vbr_rh:
+            {
+
+                p.apply_preset(gfp, 500 - (gfp.VBR_q * 10), 0);
+
+                gfc.PSY.mask_adjust = gfp.maskingadjust;
+                gfc.PSY.mask_adjust_short = gfp.maskingadjust_short;
+
+                /*
+                 * sfb21 extra only with MPEG-1 at higher sampling rates
+                 */
+                if (gfp.experimentalY)
+                    gfc.sfb21_extra = false;
+                else
+                    gfc.sfb21_extra = (gfp.out_samplerate > 44000);
+
+                /*
+                 * VBR needs at least the output of GPSYCHO, so we have to garantee
+                 * that by setting a minimum quality level, actually level 6 does
+                 * it. down to level 6
+                 */
+                if (gfp.quality > 6)
+                    gfp.quality = 6;
+
+                if (gfp.quality < 0)
+                    gfp.quality = LAME_DEFAULT_QUALITY;
+
+                gfc.iteration_loop = new VBROldIterationLoop(qu);
+                break;
+            }
+
+            default: /* cbr/abr */
+            {
+                var vbrmode;
+
+                /*
+                 * no sfb21 extra with CBR code
+                 */
+                gfc.sfb21_extra = false;
+
+                if (gfp.quality < 0)
+                    gfp.quality = LAME_DEFAULT_QUALITY;
+
+                vbrmode = gfp.VBR;
+                if (vbrmode == VbrMode.vbr_off)
+                    gfp.VBR_mean_bitrate_kbps = gfp.brate;
+                /* second, set parameters depending on bitrate */
+                p.apply_preset(gfp, gfp.VBR_mean_bitrate_kbps, 0);
+                gfp.VBR = vbrmode;
+
+                gfc.PSY.mask_adjust = gfp.maskingadjust;
+                gfc.PSY.mask_adjust_short = gfp.maskingadjust_short;
+
+                if (vbrmode == VbrMode.vbr_off) {
+                    gfc.iteration_loop = new CBRNewIterationLoop(qu);
+                } else {
+                    abort();//fix cc 精简
+                }
+                break;
+            }
+        }
+        /* initialize default values common for all modes */
+
+        if (gfp.VBR != VbrMode.vbr_off) { /* choose a min/max bitrate for VBR */
+            abort();//fix cc 精简
+        }
+
+        /* just another daily changing developer switch */
+        if (gfp.tune) {
+            abort();//fix cc 精简
+        }
+
+        /* initialize internal qval settings */
+        lame_init_qval(gfp);
+        /*
+         * automatic ATH adjustment on
+         */
+        if (gfp.athaa_type < 0)
+            gfc.ATH.useAdjust = 3;
+        else
+            gfc.ATH.useAdjust = gfp.athaa_type;
+
+        /* initialize internal adaptive ATH settings -jd */
+        gfc.ATH.aaSensitivityP = Math.pow(10.0, gfp.athaa_sensitivity
+            / -10.0);
+
+        if (gfp.short_blocks == null) {
+            gfp.short_blocks = ShortBlock.short_block_allowed;
+        }
+
+        /*
+         * Note Jan/2003: Many hardware decoders cannot handle short blocks in
+         * regular stereo mode unless they are coupled (same type in both
+         * channels) it is a rare event (1 frame per min. or so) that LAME would
+         * use uncoupled short blocks, so lets turn them off until we decide how
+         * to handle this. No other encoders allow uncoupled short blocks, even
+         * though it is in the standard.
+         */
+        /*
+         * rh 20040217: coupling makes no sense for mono and dual-mono streams
+         */
+        if (gfp.short_blocks == ShortBlock.short_block_allowed
+            && (gfp.mode == MPEGMode.JOINT_STEREO || gfp.mode == MPEGMode.STEREO)) {
+            gfp.short_blocks = ShortBlock.short_block_coupled;
+        }
+
+        if (gfp.quant_comp < 0)
+            gfp.quant_comp = 1;
+        if (gfp.quant_comp_short < 0)
+            gfp.quant_comp_short = 0;
+
+        if (gfp.msfix < 0)
+            gfp.msfix = 0;
+
+        /* select psychoacoustic model */
+        gfp.exp_nspsytune = gfp.exp_nspsytune | 1;
+
+        if (gfp.internal_flags.nsPsy.attackthre < 0)
+            gfp.internal_flags.nsPsy.attackthre = PsyModel.NSATTACKTHRE;
+        if (gfp.internal_flags.nsPsy.attackthre_s < 0)
+            gfp.internal_flags.nsPsy.attackthre_s = PsyModel.NSATTACKTHRE_S;
+
+
+        if (gfp.scale < 0)
+            gfp.scale = 1;
+
+        if (gfp.ATHtype < 0)
+            gfp.ATHtype = 4;
+
+        if (gfp.ATHcurve < 0)
+            gfp.ATHcurve = 4;
+
+        if (gfp.athaa_loudapprox < 0)
+            gfp.athaa_loudapprox = 2;
+
+        if (gfp.interChRatio < 0)
+            gfp.interChRatio = 0;
+
+        if (gfp.useTemporal == null)
+            gfp.useTemporal = true;
+        /* on by default */
+
+        /*
+         * padding method as described in
+         * "MPEG-Layer3 / Bitstream Syntax and Decoding" by Martin Sieler, Ralph
+         * Sperschneider
+         *
+         * note: there is no padding for the very first frame
+         *
+         * Robert Hegemann 2000-06-22
+         */
+        gfc.slot_lag = gfc.frac_SpF = 0;
+        if (gfp.VBR == VbrMode.vbr_off)
+            gfc.slot_lag = gfc.frac_SpF = (((gfp.version + 1) * 72000 * gfp.brate) % gfp.out_samplerate) | 0;
+
+        qupvt.iteration_init(gfp);
+        psy.psymodel_init(gfp);
+        return 0;
+    }
+
+    function update_inbuffer_size(gfc, nsamples) {
+        if (gfc.in_buffer_0 == null || gfc.in_buffer_nsamples < nsamples) {
+            gfc.in_buffer_0 = new_float(nsamples);
+            gfc.in_buffer_1 = new_float(nsamples);
+            gfc.in_buffer_nsamples = nsamples;
+        }
+    }
+
+    this.lame_encode_flush = function (gfp, mp3buffer, mp3bufferPos, mp3buffer_size) {
+        var gfc = gfp.internal_flags;
+        var buffer = new_short_n([2, 1152]);
+        var imp3 = 0, mp3count, mp3buffer_size_remaining;
+
+        /*
+         * we always add POSTDELAY=288 padding to make sure granule with real
+         * data can be complety decoded (because of 50% overlap with next
+         * granule
+         */
+        var end_padding;
+        var frames_left;
+        var samples_to_encode = gfc.mf_samples_to_encode - Encoder.POSTDELAY;
+        var mf_needed = calcNeeded(gfp);
+
+        /* Was flush already called? */
+        if (gfc.mf_samples_to_encode < 1) {
+            return 0;
+        }
+        mp3count = 0;
+
+        if (gfp.in_samplerate != gfp.out_samplerate) {
+            abort();//fix cc 精简
+        }
+        end_padding = gfp.framesize - (samples_to_encode % gfp.framesize);
+        if (end_padding < 576)
+            end_padding += gfp.framesize;
+        gfp.encoder_padding = end_padding;
+
+        frames_left = (samples_to_encode + end_padding) / gfp.framesize;
+
+        /*
+         * send in a frame of 0 padding until all internal sample buffers are
+         * flushed
+         */
+        while (frames_left > 0 && imp3 >= 0) {
+            var bunch = mf_needed - gfc.mf_size;
+            var frame_num = gfp.frameNum;
+
+            bunch *= gfp.in_samplerate;
+            bunch /= gfp.out_samplerate;
+            if (bunch > 1152)
+                bunch = 1152;
+            if (bunch < 1)
+                bunch = 1;
+
+            mp3buffer_size_remaining = mp3buffer_size - mp3count;
+
+            /* if user specifed buffer size = 0, dont check size */
+            if (mp3buffer_size == 0)
+                mp3buffer_size_remaining = 0;
+
+            imp3 = this.lame_encode_buffer(gfp, buffer[0], buffer[1], bunch,
+                mp3buffer, mp3bufferPos, mp3buffer_size_remaining);
+
+            mp3bufferPos += imp3;
+            mp3count += imp3;
+            frames_left -= (frame_num != gfp.frameNum) ? 1 : 0;
+        }
+        /*
+         * Set gfc.mf_samples_to_encode to 0, so we may detect and break loops
+         * calling it more than once in a row.
+         */
+        gfc.mf_samples_to_encode = 0;
+
+        if (imp3 < 0) {
+            /* some type of fatal error */
+            return imp3;
+        }
+
+        mp3buffer_size_remaining = mp3buffer_size - mp3count;
+        /* if user specifed buffer size = 0, dont check size */
+        if (mp3buffer_size == 0)
+            mp3buffer_size_remaining = 0;
+
+        /* mp3 related stuff. bit buffer might still contain some mp3 data */
+        bs.flush_bitstream(gfp);
+        imp3 = bs.copy_buffer(gfc, mp3buffer, mp3bufferPos,
+            mp3buffer_size_remaining, 1);
+        if (imp3 < 0) {
+            /* some type of fatal error */
+            return imp3;
+        }
+        mp3bufferPos += imp3;
+        mp3count += imp3;
+        mp3buffer_size_remaining = mp3buffer_size - mp3count;
+        /* if user specifed buffer size = 0, dont check size */
+        if (mp3buffer_size == 0)
+            mp3buffer_size_remaining = 0;
+
+        if (gfp.write_id3tag_automatic) {
+            abort();//fix cc 精简
+        }
+        return mp3count;
+    };
+
+    this.lame_encode_buffer = function (gfp, buffer_l, buffer_r, nsamples, mp3buf, mp3bufPos, mp3buf_size) {
+        var gfc = gfp.internal_flags;
+        var in_buffer = [null, null];
+
+        if (gfc.Class_ID != LAME_ID)
+            return -3;
+
+        if (nsamples == 0)
+            return 0;
+
+        update_inbuffer_size(gfc, nsamples);
+
+        in_buffer[0] = gfc.in_buffer_0;
+        in_buffer[1] = gfc.in_buffer_1;
+
+        /* make a copy of input buffer, changing type to sample_t */
+        for (var i = 0; i < nsamples; i++) {
+            in_buffer[0][i] = buffer_l[i];
+            if (gfc.channels_in > 1)
+                in_buffer[1][i] = buffer_r[i];
+        }
+
+        return lame_encode_buffer_sample(gfp, in_buffer[0], in_buffer[1],
+            nsamples, mp3buf, mp3bufPos, mp3buf_size);
+    }
+
+    function calcNeeded(gfp) {
+        var mf_needed = Encoder.BLKSIZE + gfp.framesize - Encoder.FFTOFFSET;
+        /*
+         * amount needed for FFT
+         */
+        mf_needed = Math.max(mf_needed, 512 + gfp.framesize - 32);
+
+        return mf_needed;
+    }
+
+    function lame_encode_buffer_sample(gfp, buffer_l, buffer_r, nsamples, mp3buf, mp3bufPos, mp3buf_size) {
+        var gfc = gfp.internal_flags;
+        var mp3size = 0, ret, i, ch, mf_needed;
+        var mp3out;
+        var mfbuf = [null, null];
+        var in_buffer = [null, null];
+
+        if (gfc.Class_ID != LAME_ID)
+            return -3;
+
+        if (nsamples == 0)
+            return 0;
+
+        /* copy out any tags that may have been written into bitstream */
+        mp3out = bs.copy_buffer(gfc, mp3buf, mp3bufPos, mp3buf_size, 0);
+        if (mp3out < 0)
+            return mp3out;
+        /* not enough buffer space */
+        mp3bufPos += mp3out;
+        mp3size += mp3out;
+
+        in_buffer[0] = buffer_l;
+        in_buffer[1] = buffer_r;
+
+        /* Apply user defined re-scaling */
+
+        /* user selected scaling of the samples */
+        if (BitStream.NEQ(gfp.scale, 0) && BitStream.NEQ(gfp.scale, 1.0)) {
+            for (i = 0; i < nsamples; ++i) {
+                in_buffer[0][i] *= gfp.scale;
+                if (gfc.channels_out == 2)
+                    in_buffer[1][i] *= gfp.scale;
+            }
+        }
+
+        /* user selected scaling of the channel 0 (left) samples */
+        if (BitStream.NEQ(gfp.scale_left, 0)
+            && BitStream.NEQ(gfp.scale_left, 1.0)) {
+            for (i = 0; i < nsamples; ++i) {
+                in_buffer[0][i] *= gfp.scale_left;
+            }
+        }
+
+        /* user selected scaling of the channel 1 (right) samples */
+        if (BitStream.NEQ(gfp.scale_right, 0)
+            && BitStream.NEQ(gfp.scale_right, 1.0)) {
+            for (i = 0; i < nsamples; ++i) {
+                in_buffer[1][i] *= gfp.scale_right;
+            }
+        }
+
+        /* Downsample to Mono if 2 channels in and 1 channel out */
+        if (gfp.num_channels == 2 && gfc.channels_out == 1) {
+            abort();//fix cc 精简 stereo
+        }
+
+        mf_needed = calcNeeded(gfp);
+
+        mfbuf[0] = gfc.mfbuf[0];
+        mfbuf[1] = gfc.mfbuf[1];
+
+        var in_bufferPos = 0;
+        while (nsamples > 0) {
+            var in_buffer_ptr = [null, null];
+            var n_in = 0;
+            /* number of input samples processed with fill_buffer */
+            var n_out = 0;
+            /* number of samples output with fill_buffer */
+            /* n_in <> n_out if we are resampling */
+
+            in_buffer_ptr[0] = in_buffer[0];
+            in_buffer_ptr[1] = in_buffer[1];
+            /* copy in new samples into mfbuf, with resampling */
+            var inOut = new InOut();
+            fill_buffer(gfp, mfbuf, in_buffer_ptr, in_bufferPos, nsamples,
+                inOut);
+            n_in = inOut.n_in;
+            n_out = inOut.n_out;
+
+            /* compute ReplayGain of resampled input if requested */
+            if (gfc.findReplayGain && !gfc.decode_on_the_fly)
+                if (ga.AnalyzeSamples(gfc.rgdata, mfbuf[0], gfc.mf_size,
+                        mfbuf[1], gfc.mf_size, n_out, gfc.channels_out) == GainAnalysis.GAIN_ANALYSIS_ERROR)
+                    return -6;
+
+            /* update in_buffer counters */
+            nsamples -= n_in;
+            in_bufferPos += n_in;
+            if (gfc.channels_out == 2)
+                ;// in_bufferPos += n_in;
+
+            /* update mfbuf[] counters */
+            gfc.mf_size += n_out;
+
+            /*
+             * lame_encode_flush may have set gfc.mf_sample_to_encode to 0 so we
+             * have to reinitialize it here when that happened.
+             */
+            if (gfc.mf_samples_to_encode < 1) {
+                abort();//fix cc 精简
+            }
+            gfc.mf_samples_to_encode += n_out;
+
+            if (gfc.mf_size >= mf_needed) {
+                /* encode the frame. */
+                /* mp3buf = pointer to current location in buffer */
+                /* mp3buf_size = size of original mp3 output buffer */
+                /* = 0 if we should not worry about the */
+                /* buffer size because calling program is */
+                /* to lazy to compute it */
+                /* mp3size = size of data written to buffer so far */
+                /* mp3buf_size-mp3size = amount of space avalable */
+
+                var buf_size = mp3buf_size - mp3size;
+                if (mp3buf_size == 0)
+                    buf_size = 0;
+
+                ret = lame_encode_frame(gfp, mfbuf[0], mfbuf[1], mp3buf,
+                    mp3bufPos, buf_size);
+
+                if (ret < 0)
+                    return ret;
+                mp3bufPos += ret;
+                mp3size += ret;
+
+                /* shift out old samples */
+                gfc.mf_size -= gfp.framesize;
+                gfc.mf_samples_to_encode -= gfp.framesize;
+                for (ch = 0; ch < gfc.channels_out; ch++)
+                    for (i = 0; i < gfc.mf_size; i++)
+                        mfbuf[ch][i] = mfbuf[ch][i + gfp.framesize];
+            }
+        }
+
+        return mp3size;
+    }
+
+    function lame_encode_frame(gfp, inbuf_l, inbuf_r, mp3buf, mp3bufPos, mp3buf_size) {
+        var ret = self.enc.lame_encode_mp3_frame(gfp, inbuf_l, inbuf_r, mp3buf,
+            mp3bufPos, mp3buf_size);
+        gfp.frameNum++;
+        return ret;
+    }
+
+    function InOut() {
+        this.n_in = 0;
+        this.n_out = 0;
+    }
+
+    //fix cc 精简
+
+    function fill_buffer(gfp, mfbuf, in_buffer, in_bufferPos, nsamples, io) {
+        var gfc = gfp.internal_flags;
+
+        /* copy in new samples into mfbuf, with resampling if necessary */
+        if ((gfc.resample_ratio < .9999) || (gfc.resample_ratio > 1.0001)) {
+            abort();//fix cc 精简
+        } else {
+            io.n_out = Math.min(gfp.framesize, nsamples);
+            io.n_in = io.n_out;
+            for (var i = 0; i < io.n_out; ++i) {
+                mfbuf[0][gfc.mf_size + i] = in_buffer[0][in_bufferPos + i];
+                if (gfc.channels_out == 2)
+                    mfbuf[1][gfc.mf_size + i] = in_buffer[1][in_bufferPos + i];
+            }
+        }
+    }
+
+}
+
+
+
+function GetAudio() {
+    var parse;
+    var mpg;
+
+    this.setModules = function (parse2, mpg2) {
+        parse = parse2;
+        mpg = mpg2;
+    }
+}
+
+
+function Parse() {
+    var ver;
+    var id3;
+    var pre;
+
+    this.setModules = function (ver2, id32, pre2) {
+        ver = ver2;
+        id3 = id32;
+        pre = pre2;
+    }
+}
+
+function MPGLib() {
+}
+
+function ID3Tag() {
+    var bits;
+    var ver;
+
+    this.setModules = function (_bits, _ver) {
+        bits = _bits;
+        ver = _ver;
+    }
+}
+
+function Mp3Encoder(channels, samplerate, kbps) {
+    if (channels!=1) { //精简后的代码不支持双声道
+        abort("fix cc: only supports mono")
+    }
+    var lame = new Lame();
+    var gaud = new GetAudio();
+    var ga = new GainAnalysis();
+    var bs = new BitStream();
+    var p = new Presets();
+    var qupvt = new QuantizePVT();
+    var qu = new Quantize();
+    var vbr = new VBRTag();
+    var ver = new Version();
+    var id3 = new ID3Tag();
+    var rv = new Reservoir();
+    var tak = new Takehiro();
+    var parse = new Parse();
+    var mpg = new MPGLib();
+
+    lame.setModules(ga, bs, p, qupvt, qu, vbr, ver, id3, mpg);
+    bs.setModules(ga, mpg, ver, vbr);
+    id3.setModules(bs, ver);
+    p.setModules(lame);
+    qu.setModules(bs, rv, qupvt, tak);
+    qupvt.setModules(tak, rv, lame.enc.psy);
+    rv.setModules(bs);
+    tak.setModules(qupvt);
+    vbr.setModules(lame, bs, ver);
+    gaud.setModules(parse, mpg);
+    parse.setModules(ver, id3, p);
+
+    var gfp = lame.lame_init();
+
+    gfp.num_channels = channels;
+    gfp.in_samplerate = samplerate;
+    gfp.out_samplerate = samplerate;//fix by xiangyuecn 2018-12-6 01:48:12 64kbps以下可能无声音,手动控制输出码率
+    gfp.brate = kbps;
+    gfp.mode = MPEGMode.STEREO;
+    gfp.quality = 3;
+    gfp.bWriteVbrTag = false;
+    gfp.disable_reservoir = true;
+    gfp.write_id3tag_automatic = false;
+
+    var retcode = lame.lame_init_params(gfp);
+    var maxSamples = 1152;
+    var mp3buf_size = 0 | (1.25 * maxSamples + 7200);
+    var mp3buf = new_byte(mp3buf_size);
+
+    this.encodeBuffer = function (left, right) {
+        if (channels == 1) {
+            right = left;
+        }
+        if (left.length > maxSamples) {
+            maxSamples = left.length;
+            mp3buf_size = 0 | (1.25 * maxSamples + 7200);
+            mp3buf = new_byte(mp3buf_size);
+        }
+
+        var _sz = lame.lame_encode_buffer(gfp, left, right, left.length, mp3buf, 0, mp3buf_size);
+        return new Int8Array(mp3buf.subarray(0, _sz));
+    };
+
+    this.flush = function () {
+        var _sz = lame.lame_encode_flush(gfp, mp3buf, 0, mp3buf_size);
+        return new Int8Array(mp3buf.subarray(0, _sz));
+    };
+}
+
+//fix 精简
+L3Side.SFBMAX = (Encoder.SBMAX_s * 3);
+//testFullLength();
+lamejs.Mp3Encoder = Mp3Encoder;
+}
+//fs=require('fs');
+lamejs();
+
+
+Recorder.lamejs=lamejs;
+
+
+}));

+ 533 - 0
src/engine/mp3.js

@@ -0,0 +1,533 @@
+/*
+mp3编码器,需带上src/engine/mp3-engine.js引擎使用
+https://github.com/xiangyuecn/Recorder
+
+当然最佳推荐使用mp3、wav格式,代码也是优先照顾这两种格式
+浏览器支持情况
+https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var SampleS="48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000";
+var BitS="8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 192, 224, 256, 320";
+Recorder.prototype.enc_mp3={
+	stable:true,takeEC:"full"
+	,getTestMsg:function(){
+		return $T("Zm7L::采样率范围:{1};比特率范围:{2}(不同比特率支持的采样率范围不同,小于32kbps时采样率需小于32000)",0,SampleS,BitS);
+	}
+};
+
+var NormalizeSet=function(set){
+	var bS=set.bitRate, sS=set.sampleRate,s=sS;
+	if((" "+BitS+",").indexOf(" "+bS+",")==-1){
+		Recorder.CLog($T("eGB9::{1}不在mp3支持的取值范围:{2}",0,"bitRate="+bS,BitS),3);
+	}
+	if((" "+SampleS+",").indexOf(" "+sS+",")==-1){//engine SmpFrqIndex函数会检测
+		var arr=SampleS.split(", "),vs=[];
+		for(var i=0;i<arr.length;i++) vs.push({v:+arr[i],s:Math.abs(arr[i]-sS)});
+		vs.sort(function(a,b){return a.s-b.s}); s=vs[0].v;
+		
+		set.sampleRate=s;
+		Recorder.CLog($T("zLTa::sampleRate已更新为{1},因为{2}不在mp3支持的取值范围:{3}",0,s,sS,SampleS),3);
+	}
+};
+var ImportEngineErr=function(){
+	return $T.G("NeedImport-2",["mp3.js","src/engine/mp3-engine.js"]);
+};
+//是否支持web worker
+var HasWebWorker=isBrowser && typeof Worker=="function";
+
+
+
+//*******标准UI线程转码支持函数************
+
+Recorder.prototype.mp3=function(res,True,False){
+		var This=this,set=This.set,size=res.length;
+		if(!Recorder.lamejs){
+			False(ImportEngineErr()); return;
+		};
+		
+		//优先采用worker编码,非worker时用老方法提供兼容
+		if(HasWebWorker){
+			var ctx=This.mp3_start(set);
+			if(ctx){
+				if(ctx.isW){
+					This.mp3_encode(ctx,res);
+					This.mp3_complete(ctx,True,False,1);
+					return;
+				}
+				This.mp3_stop(ctx);
+			};
+		};
+		
+		NormalizeSet(set);
+		//https://github.com/wangpengfei15975/recorder.js
+		//https://github.com/zhuker/lamejs bug:采样率必须和源一致,不然8k时没有声音,有问题fix:https://github.com/zhuker/lamejs/pull/11
+		var mp3=new Recorder.lamejs.Mp3Encoder(1,set.sampleRate,set.bitRate);
+		
+		var blockSize=57600;
+		var memory=new Int8Array(500000), mOffset=0;
+		
+		var idx=0,isFlush=0;
+		var run=function(){
+			try{
+				if(idx<size){
+					var buf=mp3.encodeBuffer(res.subarray(idx,idx+blockSize));
+				}else{
+					isFlush=1;
+					var buf=mp3.flush();
+				};
+			}catch(e){ //精简代码调用了abort
+				console.error(e);
+				if(!isFlush) try{ mp3.flush() }catch(r){ console.error(r) }
+				False("MP3 Encoder: "+e.message);
+				return;
+			};
+			var bufLen=buf.length;
+			if(bufLen>0){
+				if(mOffset+bufLen>memory.length){
+					var tmp=new Int8Array(memory.length+Math.max(500000,bufLen));
+					tmp.set(memory.subarray(0, mOffset));
+					memory=tmp;
+				}
+				memory.set(buf,mOffset);
+				mOffset+=bufLen;
+			};
+			
+			if(idx<size){
+				idx+=blockSize;
+				setTimeout(run);//尽量避免卡ui
+			}else{
+				var data=[memory.buffer.slice(0,mOffset)];
+				//去掉开头的标记信息帧
+				var meta=mp3TrimFix.fn(data,mOffset,size,set.sampleRate);
+				mp3TrimFixSetMeta(meta,set);
+				
+				True(data[0]||new ArrayBuffer(0),"audio/mp3");
+			};
+		};
+		run();
+	}
+
+
+//********边录边转码(Worker)支持函数,如果提供就代表可能支持,否则只支持标准转码*********
+
+//全局共享一个Worker,后台串行执行。如果每次都开一个新的,编码速度可能会慢很多,可能是浏览器运行缓存的因素,并且可能瞬间产生多个并行操作占用大量cpu
+var mp3Worker;
+Recorder.BindDestroy("mp3Worker",function(){
+	if(mp3Worker){
+		Recorder.CLog("mp3Worker Destroy");
+		mp3Worker.terminate();
+		mp3Worker=null;
+	};
+});
+
+
+Recorder.prototype.mp3_envCheck=function(envInfo,set){//检查环境下配置是否可用
+	var errMsg="";
+	//需要实时编码返回数据,此时需要检查是否可实时编码
+	if(set.takeoffEncodeChunk){
+		if(!newContext()){//浏览器不能创建实时编码环境
+			errMsg=$T("yhUs::当前浏览器版本太低,无法实时处理");
+		};
+	};
+	if(!errMsg && !Recorder.lamejs){
+		errMsg=ImportEngineErr();
+	};
+	return errMsg;
+};
+Recorder.prototype.mp3_start=function(set){//如果返回null代表不支持
+	return newContext(set);
+};
+var openList={id:0};
+var newContext=function(setOrNull,_badW){
+	//独立运行的函数,scope.wkScope worker.onmessage 字符串会被替换
+	var run=function(e){
+		var ed=e.data;
+		var wk_ctxs=scope.wkScope.wk_ctxs;
+		var wk_lame=scope.wkScope.wk_lame;
+		var wk_mp3TrimFix=scope.wkScope.wk_mp3TrimFix;
+		
+		var cur=wk_ctxs[ed.id];
+		if(ed.action=="init"){
+			wk_ctxs[ed.id]={
+				sampleRate:ed.sampleRate
+				,bitRate:ed.bitRate
+				,takeoff:ed.takeoff
+				
+				,pcmSize:0
+				,memory:new Int8Array(500000), mOffset:0
+				,encObj:new wk_lame.Mp3Encoder(1,ed.sampleRate,ed.bitRate)
+			};
+		}else if(!cur){
+			return;
+		};
+		var addBytes=function(buf){
+			var bufLen=buf.length;
+			if(cur.mOffset+bufLen>cur.memory.length){
+				var tmp=new Int8Array(cur.memory.length+Math.max(500000,bufLen));
+				tmp.set(cur.memory.subarray(0, cur.mOffset));
+				cur.memory=tmp;
+			}
+			cur.memory.set(buf,cur.mOffset);
+			cur.mOffset+=bufLen;
+		};
+		
+		switch(ed.action){
+		case "stop":
+			if(!cur.isCp) try{ cur.encObj.flush() }catch(e){ console.error(e) }
+			cur.encObj=null;
+			delete wk_ctxs[ed.id];
+			break;
+		case "encode":
+			if(cur.isCp)break;
+			cur.pcmSize+=ed.pcm.length;
+			try{
+				var buf=cur.encObj.encodeBuffer(ed.pcm);
+			}catch(e){ //精简代码调用了abort
+				cur.err=e;
+				console.error(e);
+			};
+			if(buf && buf.length>0){
+				if(cur.takeoff){
+					worker.onmessage({action:"takeoff",id:ed.id,chunk:buf});
+				}else{
+					addBytes(buf);
+				};
+			};
+			break;
+		case "complete":
+			cur.isCp=1;
+			try{
+				var buf=cur.encObj.flush();
+			}catch(e){ //精简代码调用了abort
+				cur.err=e;
+				console.error(e);
+			};
+			if(buf && buf.length>0){
+				if(cur.takeoff){
+					worker.onmessage({action:"takeoff",id:ed.id,chunk:buf});
+				}else{
+					addBytes(buf);
+				};
+			};
+			if(cur.err){
+				worker.onmessage({action:ed.action,id:ed.id
+					,err:"MP3 Encoder: "+cur.err.message});
+				break;
+			};
+			
+			var data=[cur.memory.buffer.slice(0,cur.mOffset)];
+			//去掉开头的标记信息帧
+			var meta=wk_mp3TrimFix.fn(data,cur.mOffset,cur.pcmSize,cur.sampleRate);
+			
+			worker.onmessage({
+				action:ed.action
+				,id:ed.id
+				,blob:data[0]||new ArrayBuffer(0)
+				,meta:meta
+			});
+			break;
+		};
+	};
+	
+	var initOnMsg=function(isW){
+		worker.onmessage=function(e){
+			var data=e; if(isW)data=e.data;
+			var ctx=openList[data.id];
+			if(ctx){
+				if(data.action=="takeoff"){
+					//取走实时生成的mp3数据
+					ctx.set.takeoffEncodeChunk(new Uint8Array(data.chunk.buffer));
+				}else{
+					//complete
+					ctx.call&&ctx.call(data);
+					ctx.call=null;
+				};
+			};
+		};
+	};
+	var initCtx=function(){
+		var ctx={worker:worker,set:setOrNull};
+		if(setOrNull){
+			ctx.id=++openList.id;
+			openList[ctx.id]=ctx;
+			
+			NormalizeSet(setOrNull);
+			
+			worker.postMessage({
+				action:"init"
+				,id:ctx.id
+				,sampleRate:setOrNull.sampleRate
+				,bitRate:setOrNull.bitRate
+				,takeoff:!!setOrNull.takeoffEncodeChunk
+				
+				,x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray
+			});
+		}else{
+			worker.postMessage({
+				x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray
+			});
+		};
+		return ctx;
+	};
+	var scope,worker=mp3Worker;
+	
+	//非浏览器,不支持worker,或者开启失败,使用UI线程处理
+	if(_badW || !HasWebWorker){
+		Recorder.CLog($T("k9PT::当前环境不支持Web Worker,mp3实时编码器运行在主线程中"),3);
+		worker={ postMessage:function(ed){ run({data:ed}); } };
+		scope={wkScope:{
+			wk_ctxs:{}, wk_lame:Recorder.lamejs, wk_mp3TrimFix:mp3TrimFix
+		}};
+		initOnMsg();
+		return initCtx();
+	};
+	
+	try{
+		if(!worker){
+			//创建一个新Worker
+			var onmsg=(run+"").replace(/[\w\$]+\.onmessage/g,"self.postMessage");
+			onmsg=onmsg.replace(/[\w\$]+\.wkScope/g,"wkScope");
+			var jsCode=");wk_lame();self.onmessage="+onmsg;
+			jsCode+=";var wkScope={ wk_ctxs:{},wk_lame:wk_lame";
+			jsCode+=",wk_mp3TrimFix:{rm:"+mp3TrimFix.rm+",fn:"+mp3TrimFix.fn+"} }";
+			
+			var lamejsCode=Recorder.lamejs.toString();
+			var url=(window.URL||webkitURL).createObjectURL(new Blob(["var wk_lame=(",lamejsCode,jsCode], {type:"text/javascript"}));
+			
+			worker=new Worker(url);
+			setTimeout(function(){
+				(window.URL||webkitURL).revokeObjectURL(url);//必须要释放,不然每次调用内存都明显泄露内存
+			},10000);//chrome 83 file协议下如果直接释放,将会使WebWorker无法启动
+			initOnMsg(1);
+		};
+		
+		var ctx=initCtx(); ctx.isW=1;
+		mp3Worker=worker;
+		return ctx;
+	}catch(e){//出错了就不要提供了
+		worker&&worker.terminate();
+		console.error(e);
+		return newContext(setOrNull, 1);//切换到UI线程处理
+	};
+};
+Recorder.prototype.mp3_stop=function(startCtx){
+	if(startCtx&&startCtx.worker){
+		startCtx.worker.postMessage({
+			action:"stop"
+			,id:startCtx.id
+		});
+		startCtx.worker=null;
+		delete openList[startCtx.id];
+		
+		//疑似泄露检测 排除id
+		var opens=-1;
+		for(var k in openList){
+			opens++;
+		};
+		if(opens){
+			Recorder.CLog($T("fT6M::mp3 worker剩{1}个未stop",0,opens),3);
+		};
+	};
+};
+Recorder.prototype.mp3_encode=function(startCtx,pcm){
+	if(startCtx&&startCtx.worker){
+		startCtx.worker.postMessage({
+			action:"encode"
+			,id:startCtx.id
+			,pcm:pcm
+		});
+	};
+};
+Recorder.prototype.mp3_complete=function(startCtx,True,False,autoStop){
+	var This=this;
+	if(startCtx&&startCtx.worker){
+		startCtx.call=function(data){
+			if(autoStop){
+				This.mp3_stop(startCtx);
+			};
+			if(data.err){
+				False(data.err);
+			}else{
+				mp3TrimFixSetMeta(data.meta,startCtx.set);
+				True(data.blob,"audio/mp3");
+			};
+		};
+		startCtx.worker.postMessage({
+			action:"complete"
+			,id:startCtx.id
+		});
+	}else{
+		False($T("mPxH::mp3编码器未start"));
+	};
+};
+
+
+
+
+
+
+
+//*******辅助函数************
+
+/*读取lamejs编码出来的mp3信息,只能读特定格式,如果读取失败返回null
+mp3Buffers=[ArrayBuffer,...]
+length=mp3Buffers的数据二进制总长度
+*/
+Recorder.mp3ReadMeta=function(mp3Buffers,length){
+	//kill babel-polyfill ES6 Number.parseInt 不然放到Worker里面找不到方法,也不能用typeof(x)==object 会被替换成 _typeof
+	var parseInt_ES3=typeof(window)!="undefined"&&window.parseInt||typeof(self)!="undefined"&&self.parseInt||parseInt;
+	
+	var u8arr0=new Uint8Array(mp3Buffers[0]||[]);
+	if(u8arr0.length<4){
+		return null;
+	};
+	var byteAt=function(idx,u8){
+		return ("0000000"+((u8||u8arr0)[idx]||0).toString(2)).substr(-8);
+	};
+	var b2=byteAt(0)+byteAt(1);
+	var b4=byteAt(2)+byteAt(3);
+	
+	if(!/^1{11}/.test(b2)){//未发现帧同步
+		return null;
+	};
+	var version=({"00":2.5,"10":2,"11":1})[b2.substr(11,2)];
+	var layer=({"01":3})[b2.substr(13,2)];//仅支持Layer3
+	var sampleRate=({ //lamejs -> Tables.samplerate_table
+		"1":[44100, 48000, 32000]
+		,"2":[22050, 24000, 16000]
+		,"2.5":[11025, 12000, 8000]
+	})[version];
+	sampleRate&&(sampleRate=sampleRate[parseInt_ES3(b4.substr(4,2),2)]);
+	var bitRate=[ //lamejs -> Tables.bitrate_table
+		[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160] //MPEG 2 2.5
+		,[0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]//MPEG 1
+	][version==1?1:0][parseInt_ES3(b4.substr(0,4),2)];
+	
+	if(!version || !layer || !bitRate || !sampleRate){
+		return null;
+	};
+	
+	var duration=Math.round(length*8/bitRate);
+	var frame=layer==1?384:layer==2?1152:version==1?1152:576;
+	var frameDurationFloat=frame/sampleRate*1000;
+	var frameSize=Math.floor((frame*bitRate)/8/sampleRate*1000);
+	
+	//检测是否存在Layer3帧填充1字节。这里只获取第二帧的填充信息,首帧永远没有填充。其他帧可能隔一帧出现一个填充,或者隔很多帧出现一个填充;目测是取决于frameSize未舍入时的小数部分,因为有些采样率的frameSize会出现小数(11025、22050、44100 典型的除不尽),然后字节数无法表示这种小数,就通过一定步长来填充弥补小数部分丢失
+	var hasPadding=0,seek=0;
+	for(var i=0;i<mp3Buffers.length;i++){
+		//寻找第二帧
+		var buf=mp3Buffers[i];
+		seek+=buf.byteLength;
+		if(seek>=frameSize+3){
+			var buf8=new Uint8Array(buf);
+			var idx=buf.byteLength-(seek-(frameSize+3)+1);
+			var ib4=byteAt(idx,buf8);
+			hasPadding=ib4.charAt(6)=="1";
+			break;
+		};
+	};
+	if(hasPadding){
+		frameSize++;
+	};
+	
+	
+	return {
+		version:version //1 2 2.5 -> MPEG1 MPEG2 MPEG2.5
+		,layer:layer//3 -> Layer3
+		,sampleRate:sampleRate //采样率 hz
+		,bitRate:bitRate //比特率 kbps
+		
+		,duration:duration //音频时长 ms
+		,size:length //总长度 byte
+		,hasPadding:hasPadding //是否存在1字节填充,首帧永远没有,这个值其实代表的第二帧是否有填充,并不代表其他帧的
+		,frameSize:frameSize //每帧最大长度,含可能存在的1字节padding byte
+		,frameDurationFloat:frameDurationFloat //每帧时长,含小数 ms
+	};
+};
+
+//去掉lamejs开头的标记信息帧,免得mp3解码出来的时长比pcm的长太多
+var mp3TrimFix={//minfiy keep name
+rm:Recorder.mp3ReadMeta
+,fn:function(mp3Buffers,length,pcmLength,pcmSampleRate){
+	var meta=this.rm(mp3Buffers,length);
+	if(!meta){
+		return {err:"mp3 unknown format"};
+	};
+	var pcmDuration=Math.round(pcmLength/pcmSampleRate*1000);
+	
+	//开头多出这么多帧,移除掉;正常情况下最多为2帧
+	var num=Math.floor((meta.duration-pcmDuration)/meta.frameDurationFloat);
+	if(num>0){
+		var size=num*meta.frameSize-(meta.hasPadding?1:0);//首帧没有填充,第二帧可能有填充,这里假设最多为2帧(测试并未出现3帧以上情况),其他帧不管,就算出现了并且导致了错误后面自动容错
+		length-=size;
+		var arr0=0,arrs=[];
+		for(var i=0;i<mp3Buffers.length;i++){
+			var arr=mp3Buffers[i];
+			if(size<=0){
+				break;
+			};
+			if(size>=arr.byteLength){
+				size-=arr.byteLength;
+				arrs.push(arr);
+				mp3Buffers.splice(i,1);
+				i--;
+			}else{
+				mp3Buffers[i]=arr.slice(size);
+				arr0=arr;
+				size=0;
+			};
+		};
+		var checkMeta=this.rm(mp3Buffers,length);
+		if(!checkMeta){
+			//还原变更,应该不太可能会出现
+			arr0&&(mp3Buffers[0]=arr0);
+			for(var i=0;i<arrs.length;i++){
+				mp3Buffers.splice(i,0,arrs[i]);
+			};
+			meta.err="mp3 fix error: 已还原,错误原因不明"; //worker里面没$T翻译
+		};
+		
+		var fix=meta.trimFix={};
+		fix.remove=num;
+		fix.removeDuration=Math.round(num*meta.frameDurationFloat);
+		fix.duration=Math.round(length*8/meta.bitRate);
+	};
+	return meta;
+}
+};
+var mp3TrimFixSetMeta=function(meta,set){
+	var tag="MP3 Info: ";
+	if(meta.sampleRate&&meta.sampleRate!=set.sampleRate || meta.bitRate&&meta.bitRate!=set.bitRate){
+		Recorder.CLog(tag+$T("uY9i::和设置的不匹配{1},已更新成{2}",0,"set:"+set.bitRate+"kbps "+set.sampleRate+"hz","set:"+meta.bitRate+"kbps "+meta.sampleRate+"hz"),3,set);
+		set.sampleRate=meta.sampleRate;
+		set.bitRate=meta.bitRate;
+	};
+	
+	var trimFix=meta.trimFix;
+	if(trimFix){
+		tag+=$T("iMSm::Fix移除{1}帧",0,trimFix.remove)+" "+trimFix.removeDuration+"ms -> "+trimFix.duration+"ms";
+		if(trimFix.remove>2){
+			meta.err=(meta.err?meta.err+", ":"")+$T("b9zm::移除帧数过多");
+		};
+	}else{
+		tag+=(meta.duration||"-")+"ms";
+	};
+	
+	if(meta.err){
+		Recorder.CLog(tag,1,meta.err,meta);
+	}else{
+		Recorder.CLog(tag,meta);
+	};
+};
+
+
+	
+}));

+ 174 - 0
src/engine/pcm.js

@@ -0,0 +1,174 @@
+/*
+pcm编码器+编码引擎
+https://github.com/xiangyuecn/Recorder
+
+编码原理:本编码器输出的pcm格式数据其实就是Recorder中的buffers原始数据(经过了重新采样),16位时为LE小端模式(Little Endian),并未经过任何编码处理
+
+编码的代码和wav.js区别不大,pcm加上一个44字节wav头即成wav文件;所以要播放pcm就很简单了,直接转成wav文件来播放,已提供转换函数 Recorder.pcm2wav
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+Recorder.prototype.enc_pcm={
+	stable:true,fast:true
+	,getTestMsg:function(){
+		return $T("fWsN::pcm为未封装的原始音频数据,pcm音频文件无法直接播放,可用Recorder.pcm2wav()转码成wav播放;支持位数8位、16位(填在比特率里面),采样率取值无限制");
+	}
+};
+
+var NormalizeSet=function(set){
+	var bS=set.bitRate,b=bS==8?8:16;
+	if(bS!=b) Recorder.CLog($T("uMUJ::PCM Info: 不支持{1}位,已更新成{2}位",0,bS,b),3);
+	set.bitRate=b;
+};
+
+
+
+//*******标准UI线程转码支持函数************
+
+Recorder.prototype.pcm=function(res,True,False){
+	var set=this.set;
+	NormalizeSet(set);
+	var bytes=PcmEncode(res,set.bitRate);
+	True(bytes.buffer,"audio/pcm");
+};
+
+var PcmEncode=function(pcm,bitRate){
+	if(bitRate==8) {
+		var size=pcm.length;
+		var bytes=new Uint8Array(size);
+		for(var i=0;i<size;i++){
+			//16转8据说是雷霄骅的 https://blog.csdn.net/sevennight1989/article/details/85376149 细节比blqw的按比例的算法清晰点
+			var val=(pcm[i]>>8)+128;
+			bytes[i]=val;
+		};
+	}else{
+		pcm=new Int16Array(pcm); //复制一份
+		var bytes=new Uint8Array(pcm.buffer);
+	};
+	return bytes;
+};
+
+
+
+
+
+/**pcm直接转码成wav,可以直接用来播放;需同时引入src/engine/wav.js
+data: {
+		sampleRate:16000 pcm的采样率
+		bitRate:16 pcm的位数 取值:8 或 16
+		blob:blob对象 或 ArrayBuffer(回调也将返回ArrayBuffer)
+	}
+	data如果直接提供的blob将默认使用16位16khz的配置,仅用于测试
+True(wavBlob,duration,mime)
+False(msg)
+**/
+Recorder.pcm2wav=function(data,True,False){
+	if(!data.blob){//Blob 测试用
+		data={blob:data};
+	};
+	var blob=data.blob;
+	var sampleRate=data.sampleRate||16000,bitRate=data.bitRate||16;
+	if(!data.sampleRate || !data.bitRate){
+		Recorder.CLog($T("KmRz::pcm2wav必须提供sampleRate和bitRate"),3);
+	};
+	if(!Recorder.prototype.wav){
+		False($T.G("NeedImport-2",["pcm2wav","src/engine/wav.js"]));
+		return;
+	};
+	
+	var loadOk=function(arrB,dArrB){
+		var pcm;
+		if(bitRate==8){
+			//8位转成16位
+			var u8arr=new Uint8Array(arrB);
+			pcm=new Int16Array(u8arr.length);
+			for(var j=0;j<u8arr.length;j++){
+				pcm[j]=(u8arr[j]-128)<<8;
+			};
+		}else{
+			pcm=new Int16Array(arrB);
+		};
+		
+		var rec=Recorder({
+			type:"wav"
+			,sampleRate:sampleRate
+			,bitRate:bitRate
+		});
+		if(dArrB)rec.dataType="arraybuffer";
+		rec.mock(pcm,sampleRate).stop(function(wavBlob,duration,mime){
+			True(wavBlob,duration,mime);
+		},False);
+	};
+	
+	if(blob instanceof ArrayBuffer){
+		loadOk(blob,1);
+	}else{
+		var reader=new FileReader();
+		reader.onloadend=function(){
+			loadOk(reader.result);
+		};
+		reader.readAsArrayBuffer(blob);
+	};
+};
+
+
+
+//********边录边转码支持函数,pcm转码超快,因此也是工作在UI线程(非Worker)*********
+Recorder.prototype.pcm_envCheck=function(envInfo,set){//检查环境下配置是否可用
+	return ""; //没有需要检查的内容
+};
+
+Recorder.prototype.pcm_start=function(set){//如果返回null代表不支持
+	NormalizeSet(set);
+	return {set:set, memory:new Uint8Array(500000), mOffset:0};
+};
+var addBytes=function(cur,buf){
+	var bufLen=buf.length;
+	if(cur.mOffset+bufLen>cur.memory.length){
+		var tmp=new Uint8Array(cur.memory.length+Math.max(500000,bufLen));
+		tmp.set(cur.memory.subarray(0, cur.mOffset));
+		cur.memory=tmp;
+	}
+	cur.memory.set(buf,cur.mOffset);
+	cur.mOffset+=bufLen;
+};
+
+Recorder.prototype.pcm_stop=function(startCtx){
+	if(startCtx&&startCtx.memory){
+		startCtx.memory=null;
+	}
+};
+Recorder.prototype.pcm_encode=function(startCtx,pcm){
+	if(startCtx&&startCtx.memory){
+		var set=startCtx.set;
+		var bytes=PcmEncode(pcm, set.bitRate);
+		
+		if(set.takeoffEncodeChunk){
+			set.takeoffEncodeChunk(bytes);
+		}else{
+			addBytes(startCtx, bytes);
+		};
+	};
+};
+Recorder.prototype.pcm_complete=function(startCtx,True,False,autoStop){
+	if(startCtx&&startCtx.memory){
+		if(autoStop){
+			this.pcm_stop(startCtx);
+		};
+		var buffer=startCtx.memory.buffer.slice(0,startCtx.mOffset);
+		True(buffer,"audio/pcm");
+	}else{
+		False($T("sDkA::pcm编码器未start"));
+	};
+};
+
+
+
+
+}));

+ 122 - 0
src/engine/wav.js

@@ -0,0 +1,122 @@
+/*
+wav编码器+编码引擎
+https://github.com/xiangyuecn/Recorder
+
+当然最佳推荐使用mp3、wav格式,代码也是优先照顾这两种格式
+浏览器支持情况
+https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats
+
+编码原理:给pcm数据加上一个44字节的wav头即成wav文件;pcm数据就是Recorder中的buffers原始数据(重新采样),16位时为LE小端模式(Little Endian),实质上是未经过任何编码处理
+
+注意:其他wav编码器可能不是44字节的头,要从任意wav文件中提取pcm数据,请参考:assets/runtime-codes/fragment.decode.wav.js
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+Recorder.prototype.enc_wav={
+	stable:true,fast:true
+	,getTestMsg:function(){
+		return $T("gPSE::支持位数8位、16位(填在比特率里面),采样率取值无限制;此编码器仅在pcm数据前加了一个44字节的wav头,编码出来的16位wav文件去掉开头的44字节即可得到pcm(注:其他wav编码器可能不是44字节)");
+	}
+};
+
+var NormalizeSet=function(set){
+	var bS=set.bitRate,b=bS==8?8:16;
+	if(bS!=b) Recorder.CLog($T("wyw9::WAV Info: 不支持{1}位,已更新成{2}位",0,bS,b),3);
+	set.bitRate=b;
+};
+
+Recorder.prototype.wav=function(res,True,False){
+	var This=this,set=This.set;
+	
+	NormalizeSet(set);
+	var size=res.length,sampleRate=set.sampleRate,bitRate=set.bitRate;
+	var dataLength=size*(bitRate/8);
+	
+	//生成wav头
+	var header=Recorder.wav_header(1,1,sampleRate,bitRate,dataLength);
+	var offset=header.length;
+	var bytes=new Uint8Array(offset+dataLength);
+	bytes.set(header);
+	
+	// 写入采样数据
+	if(bitRate==8) {
+		for(var i=0;i<size;i++) {
+			//16转8据说是雷霄骅的 https://blog.csdn.net/sevennight1989/article/details/85376149 细节比blqw的按比例的算法清晰点
+			var val=(res[i]>>8)+128;
+			bytes[offset++]=val;
+		};
+	}else{
+		bytes=new Int16Array(bytes.buffer);//长度一定是偶数
+		bytes.set(res,offset/2);
+	};
+	
+	True(bytes.buffer,"audio/wav");
+};
+
+/**
+根据参数生成wav文件头,返回Uint8Array(format=1时固定返回44字节,其他返回46字节)
+format: 1 (raw pcm) 2 (ADPCM) 3 (IEEE Float) 6 (g711a) 7 (g711u)
+numCh: 声道数
+dataLength: wav中的音频数据二进制长度
+**/
+Recorder.wav_header=function(format,numCh,sampleRate,bitRate,dataLength){
+	//文件头 http://soundfile.sapp.org/doc/WaveFormat/ https://www.jianshu.com/p/63d7aa88582b https://github.com/mattdiamond/Recorderjs https://www.cnblogs.com/blqw/p/3782420.html https://www.cnblogs.com/xiaoqi/p/6993912.html
+	var extSize=format==1?0:2;
+	var buffer=new ArrayBuffer(44+extSize);
+	var data=new DataView(buffer);
+	
+	var offset=0;
+	var writeString=function(str){
+		for (var i=0;i<str.length;i++,offset++) {
+			data.setUint8(offset,str.charCodeAt(i));
+		};
+	};
+	var write16=function(v){
+		data.setUint16(offset,v,true);
+		offset+=2;
+	};
+	var write32=function(v){
+		data.setUint32(offset,v,true);
+		offset+=4;
+	};
+	
+	/* RIFF identifier */
+	writeString('RIFF');
+	/* RIFF chunk length */
+	write32(36+extSize+dataLength);
+	/* RIFF type */
+	writeString('WAVE');
+	/* format chunk identifier */
+	writeString('fmt ');
+	/* format chunk length */
+	write32(16+extSize);
+	/* audio format */
+	write16(format);
+	/* channel count */
+	write16(numCh);
+	/* sample rate */
+	write32(sampleRate);
+	/* byte rate (sample rate * block align) */
+	write32(sampleRate*(numCh*bitRate/8));// *1 声道
+	/* block align (channel count * bytes per sample) */
+	write16(numCh*bitRate/8);// *1 声道
+	/* bits per sample */
+	write16(bitRate);
+	if(format!=1){// ExtraParamSize 0
+		write16(0);
+	}
+	/* data chunk identifier */
+	writeString('data');
+	/* data chunk length */
+	write32(dataLength);
+	
+	return new Uint8Array(buffer);
+};
+
+}));

+ 910 - 0
src/extensions/asr.aliyun.short.js

@@ -0,0 +1,910 @@
+/*
+录音 Recorder扩展,ASR,阿里云语音识别(语音转文字),支持实时语音识别、单个音频文件转文字
+
+https://github.com/xiangyuecn/Recorder
+
+- 本扩展通过调用 阿里云-智能语音交互-一句话识别 接口来进行语音识别,无时长限制。
+- 识别过程中采用WebSocket直连阿里云,语音数据无需经过自己服务器。
+- 自己服务器仅需提供一个Token生成接口即可(本库已实现一个本地测试NodeJs后端程序 /assets/demo-asr/NodeJsServer_asr.aliyun.short.js)。
+
+本扩展单次语音识别时虽长无限制,最佳使用场景还是1-5分钟内的语音识别;60分钟以上的语音识别本扩展也能胜任(需自行进行重试容错处理),但太长的识别场景不太适合使用阿里云一句话识别(阿里云单次一句话识别最长60秒,本扩展自带拼接过程,所以无时长限制);为什么采用一句话识别:因为便宜。
+
+
+【对接流程】
+	1. 到阿里云开通 一句话识别 服务(可试用一段时间,正式使用时应当开通商用版,很便宜),得到AccessKey、Secret,参考:https://help.aliyun.com/document_detail/324194.html ;
+	2. 到阿里云智能语音交互控制台创建相应的语音识别项目,并配置好项目,得到Appkey,每个项目可以设置一种语言模型,要支持多种语言就创建多个项目;
+	3. 需要后端提供一个Token生成接口(用到上面的Key和Secret),可直接参考或本地运行此NodeJs后端测试程序:/assets/demo-asr/NodeJsServer_asr.aliyun.short.js,配置好代码里的阿里云账号后,在目录内直接命令行执行`node NodeJsServer_asr.aliyun.short.js`即可运行提供本地测试接口;
+	4. 前端调用ASR_Aliyun_Short,传入tokenApi,即可很简单的实现语音识别功能;
+
+在线测试例子:
+	https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.asr.aliyun.short
+调用示例:
+	var rec=Recorder(recSet);rec.open(...) //进行语音识别前,先打开录音,获得录音权限
+	
+	var asr=Recorder.ASR_Aliyun_Short(set); //创建asr对象,参数详情请参考下面的源码
+	
+	//asr创建好后,随时调用strat,开始进行语音识别
+	asr.start(function(){
+		rec.start();//一般在start成功之后,调用rec.start()开始录音,此时可以通知用户讲话了
+	},fail);
+	
+	//实时处理输入音频数据,一般是在rec.set.onProcess中调用本方法,输入实时录制的音频数据,输入的数据将会发送语音识别;不管有没有start,都可以调用本方法,start前输入的数据会缓冲起来等到start后进行识别
+	asr.input([[Int16,...],...],48000,0); 
+	
+	//话讲完后,调用stop结束语音识别,得到识别到的内容文本
+	asr.stop(function(text,abortMsg){
+		//text为识别到的最终完整内容;如果存在abortMsg代表识别中途被某种错误停止了,text是停止前的内容识别到的完整内容,一般早在asrProcess中会收到abort事件然后要停止录音
+	},fail);
+
+更多的方法:
+	asr.inputDuration() 获取input已输入的音频数据总时长,单位ms
+	asr.sendDuration() 获取已发送识别的音频数据总时长,存在重发重叠部分,因此比inputDuration长
+	asr.asrDuration() 获取已识别的音频数据总时长,去除了sendDuration的重叠部分,值<=inputDuration
+	asr.getText() 获取实时结果文本,如果已stop返回的就是最终文本,一般无需调用此方法,因为回调中都提供了此方法的返回值
+	
+	//一次性将单个完整音频Blob文件转成文字,无需start、stop,创建好asr后直接调用本方法即可
+	asr.audioToText(audioBlob,success,fail)
+	//一次性的将单个完整PCM音频数据转成文字,无需start、stop,创建好asr后直接调用本方法即可
+	asr.pcmToText(buffer,sampleRate,success,fail)
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var ASR_Aliyun_Short=function(set){
+	return new fn(set);
+};
+var ASR_Aliyun_ShortTxt="ASR_Aliyun_Short";
+var fn=function(set){
+	var This=this;
+	var o={
+		tokenApi:"" /*必填,调用阿里云一句话识别需要的token获取api地址
+				接口实现请参考本地测试NodeJs后端程序:/assets/demo-asr/NodeJsServer_asr.aliyun.short.js
+				此接口默认需要返回数据格式:
+					{
+						c:0 //code,0接口调用正常,其他数值接口调用出错
+						,m:"" //message,接口调用出错时的错误消息
+						,v:{ //value,接口成功调用返回的结果【结果中必须包含下面两个值】
+							appkey:"aaaa" //lang语言模型对应的项目appkey
+							,token:"bbbb" //语音识别Access Token
+						}
+					}
+				如果不是返回的这个格式的数据,必须提供apiRequest配置,自行请求api*/
+		,apiArgs:{ //请求tokenApi时要传的参数
+			action:"token"
+			,lang:"普通话" //语言模型设置(具体取值取决于tokenApi支持了哪些语言)
+		}
+		,apiRequest:null /*tokenApi的请求实现方法,默认使用简单的ajax实现
+				如果你接口返回的数据格式和默认格式不一致,必须提供一个函数来自行请求api
+				方法参数:fn(url,args,success,fail)
+					url:"" == tokenApi
+					args:{} == apiArgs
+					success:fn(value) 接口调用成功回调,value={appkey:"", token:""}
+					fail:fn(errMsg) 接口调用出错回调,errMsg="错误消息"
+				*/
+		,compatibleWebSocket:null /*提供一个函数返回兼容WebSocket的对象,一般也需要提供apiRequest
+				如果你使用的环境不支持WebSocket,需要提供一个函数来返回一个兼容实现对象
+				方法参数:fn(url) url为连接地址,返回一个对象,需支持的回调和方法:{
+						onopen:fn() 连接成功回调
+						onerror:fn({message}) 连接失败回调
+						onclose:fn({code, reason}) 连接关闭回调
+						onmessage:fn({data}) 收到消息回调
+						connect:fn() 进行连接
+						close:fn(code,reason) 关闭连接
+						send:fn(data) 发送数据,data为字符串或者arraybuffer
+					}
+				binaryType固定使用arraybuffer类型
+				*/
+		
+		//,asrProcess:null //fn(text,nextDuration,abortMsg) 当实时接收到语音识别结果时的回调函数(对单个完整音频文件的识别也有效)
+				//此方法需要返回true才会继续识别,否则立即当做识别超时处理,你应当通过nextDuration来决定是否继续识别,避免无限制的识别大量消耗阿里云资源额度;如果不提供本回调,默认1分钟超时后终止识别(因为没有绑定回调,你不知道已经被终止了)
+				//text为中间识别到的内容(并非已有录音片段的最终结果,后续可能会根据语境修整)
+				//nextDuration 为当前回调时下次即将进行识别的总时长,单位毫秒,通过这个参数来限制识别总时长,超过时长就返回false终止识别(第二分钟开始每分钟会多识别前一分钟结尾的5秒数据,用于两分钟之间的拼接,相当于第二分钟最多识别55秒的新内容)
+				//abortMsg如不为空代表识别中途因为某种原因终止了识别(比如超时、接口调用失败),收到此信息时应当立即调用asr的stop方法得到最终结果,并且终止录音
+		
+		,log:NOOP //fn(msg,color)提供一个日志输出接口,默认只会输出到控制台,color: 1:红色,2绿色,不为空时为颜色字符串
+		
+		//高级选项
+		,fileSpeed:6 //单个文件识别发送速度控制,取值1-n;1:为按播放速率发送,最慢,识别精度完美;6:按六倍播放速度发送,花10秒识别60秒文件比较快,精度还行;再快测试发现似乎会缺失内容,可能是发送太快底层识别不过来导致返回的结果缺失。
+	};
+	for(var k in set){
+		o[k]=set[k];
+	};
+	This.set=set=o;
+	This.state=0;//0 未start,1 start,2 stop
+	This.started=0;
+	
+	This.sampleRate=16000;//发送的采样率
+	//This.tokenData
+	
+	This.pcmBuffers=[];//等待发送的缓冲数据
+	This.pcmTotal=0;//输入的总量
+	This.pcmOffset=0;//缓冲[0]的已发送位置
+	This.pcmSend=0;//发送的总量,不会重复计算重发的量
+	
+	This.joinBuffers=[];//下一分钟左移5秒,和上一分钟重叠5秒
+	This.joinSize=0;//左移的数据量
+	This.joinSend=0;//单次已发送量
+	This.joinOffset=-1;//左移[0]的已发送位置,-1代表可以进行整理buffers
+	This.joinIsOpen=0;//是否开始发送
+	This.joinSendTotal=0;//已发送重叠的总量
+	
+	This.sendCurSize=0;//单个wss发送量,不能超过1分钟的量
+	This.sendTotal=0;//总计的发送量,存在重发重叠部分
+	
+	//This.stopWait=null 
+	//This.sendWait=0
+	//This.sendAbort=false
+	//This.sendAbortMsg=""
+	
+	//This.wsCur 当前的wss
+	//This.wsLock 新的一分钟wss准备
+	This.resTxts=[];//每分钟结果列表 resTxt object: {tempTxt:"efg",okTxt:"efgh",fullTxt:"abcdefgh"}
+	
+	if(!set.asrProcess){
+		This.log("未绑定asrProcess回调无法感知到abort事件",3);
+	};
+};
+var CLog=function(){
+	var v=arguments; v[0]="["+ASR_Aliyun_ShortTxt+"]"+v[0];
+	Recorder.CLog.apply(null,v);
+};
+fn.prototype=ASR_Aliyun_Short.prototype={
+	log:function(msg,color){
+		CLog(msg,typeof color=="number"?color:0);
+		this.set.log("["+ASR_Aliyun_ShortTxt+"]"+msg,color==3?"#f60":color);
+	}
+	
+	
+	//input已输入的音频数据总时长
+	,inputDuration:function(){
+		return Math.round(this.pcmTotal/this.sampleRate*1000);
+	}
+	//已发送识别的音频数据总时长,存在重发重叠部分,因此比inputDuration长
+	,sendDuration:function(add){
+		var size=this.sendTotal;
+		size+=add||0;
+		return Math.round(size/this.sampleRate*1000);
+	}
+	//已识别的音频数据总时长,去除了sendDuration的重叠部分,值<=inputDuration
+	,asrDuration:function(){
+		return this.sendDuration(-this.joinSendTotal);
+	}
+	
+	
+	/**一次性将单个完整音频文件转成文字,支持的文件类型由具体的浏览器决定,因此存在兼容性问题,兼容性mp3最好,wav次之,其他格式不一定能够解码。实际就是调用:浏览器解码音频得到PCM -> start -> input ... input -> stop
+		blob:Blob 音频文件Blob对象,如:rec.stop得到的录音结果、file input选择的文件、XMLHttpRequest的blob结果、new Blob([TypedArray])创建的blob
+		success fn(text,abortMsg) text为识别到的完整内容,abortMsg参考stop
+		fail:fn(errMsg)
+	**/
+	,audioToText:function(blob,success,fail){
+		var This=this;
+		var failCall=function(err){
+			This.log(err,1);
+			fail&&fail(err);
+		};
+		if(!Recorder.GetContext()){//强制激活Recorder.Ctx 不支持大概率也不支持解码
+			failCall("浏览器不支持音频解码");
+			return;
+		};
+		
+		var reader=new FileReader();
+		reader.onloadend=function(){
+			var ctx=Recorder.Ctx;
+			ctx.decodeAudioData(reader.result,function(raw){
+				var src=raw.getChannelData(0);
+				var sampleRate=raw.sampleRate;
+				
+				var pcm=new Int16Array(src.length);
+				for(var i=0;i<src.length;i++){//floatTo16BitPCM
+					var s=Math.max(-1,Math.min(1,src[i]));
+					s=s<0?s*0x8000:s*0x7FFF;
+					pcm[i]=s;
+				};
+				
+				This.pcmToText(pcm,sampleRate,success,fail);
+			},function(e){
+				failCall("音频解码失败["+blob.type+"]:"+e.message);
+			});
+		};
+		reader.readAsArrayBuffer(blob);
+	}
+	/**一次性的将单个完整音频转成文字。实际就是调用:start -> input ... input -> stop
+		buffer:[Int16,...] 16位单声道音频pcm数据,一维数组
+		sampleRate pcm的采样率
+		success fn(text,abortMsg) text为识别到的完整内容,abortMsg参考stop
+		fail:fn(errMsg)
+	**/
+	,pcmToText:function(buffer,sampleRate,success,fail){
+		var This=this;
+		This.start(function(){
+			This.log("单个文件"+Math.round(buffer.length/sampleRate*1000)+"ms转文字");
+			This.sendSpeed=This.set.fileSpeed;
+			This.input([buffer],sampleRate);
+			This.stop(success,fail);
+		},fail);
+	}
+	
+	
+	
+	/**开始识别,开始后需要调用input输入录音数据,结束时调用stop来停止识别。如果start之前调用了input输入数据,这些数据将会等到start成功之后进行识别。
+	建议在success回调中开始录音(即rec.start);当然asr.start和rec.start同时进行调用,或者任意一个先调用都是允许的,不过当出现fail时,需要处理好asr和rec各自的状态。
+	无需特殊处理start和stop的关系,只要调用了stop,会阻止未完成的start,不会执行回调。
+		success:fn()
+		fail:fn(errMsg)
+	**/
+	,start:function(success,fail){
+		var This=this,set=This.set;
+		var failCall=function(err){
+			This.sendAbortMsg=err;
+			fail&&fail(err);
+		};
+		if(!set.compatibleWebSocket){
+			if(!isBrowser){
+				failCall("非浏览器环境,请提供compatibleWebSocket配置来返回一个兼容的WebSocket");
+				return;
+			};
+		};
+		
+		if(This.state!=0){
+			failCall("ASR对象不可重复start");
+			return;
+		};
+		This.state=1;
+		
+		var stopCancel=function(){
+			This.log("ASR start被stop中断",1);
+			This._send();//调用了再说,不管什么状态
+		};
+		This._token(function(){
+			if(This.state!=1){
+				stopCancel();
+			}else{
+				This.log("OK start",2);
+				This.started=1;
+				success&&success();
+				
+				This._send();//调用了再说,不管什么状态
+			};
+		},function(err){
+			err="语音识别token接口出错:"+err;
+			This.log(err,1);
+			if(This.state!=1){
+				stopCancel();
+			}else{
+				failCall(err);
+				This._send();//调用了再说,不管什么状态
+			};
+		});
+	}
+	/**结束识别,一般在调用了本方法后,下一行代码立即调用录音rec.stop结束录音
+		success:fn(text,abortMsg) text为识别到的最终完整内容;如果存在abortMsg代表识别中途被某种错误停止了,text是停止前的内容识别到的完整内容,一般早在asrProcess中会收到abort事件然后要停止录音
+		fail:fn(errMsg)
+	**/
+	,stop:function(success,fail){
+		success=success||NOOP;
+		fail=fail||NOOP;
+		var This=this;
+		var failCall=function(err){
+			err="语音识别stop出错:"+err;
+			This.log(err,1);
+			fail(err);
+		};
+		
+		if(This.state==2){
+			failCall("ASR对象不可重复stop");
+			return;
+		};
+		This.state=2;
+		
+		This.stopWait=function(){
+			This.stopWait=null;
+			if(!This.started){
+				fail(This.sendAbortMsg||"未开始语音识别");
+				return;
+			};
+			var txt=This.getText();
+			if(!txt && This.sendAbortMsg){
+				fail(This.sendAbortMsg);//仅没有内容时,才走异常
+			}else{
+				success(txt, This.sendAbortMsg||"");//尽力返回已有内容
+			};
+		};
+		//等待数据发送完
+		This._send();
+	}
+	
+	
+	
+	/**实时处理输入音频数据;不管有没有start,都可以调用本方法,start前输入的数据会缓冲起来等到start后进行识别
+		buffers:[[Int16...],...] pcm片段列表,为二维数组,第一维数组内存放1个或多个pcm数据;比如可以是:rec.buffers、onProcess中的buffers截取的一段新二维数组
+		sampleRate:48000 buffers中pcm的采样率
+		
+		buffersOffset:0 可选,默认0,从buffers第一维的这个位置开始识别,方便rec的onProcess中使用
+	**/
+	,input:function(buffers,sampleRate  ,buffersOffset){
+		var This=this;
+		
+		if(This.state==2){//已停止,停止输入数据
+			This._send();
+			return;
+		};
+		var msg="input输入的采样率低于"+This.sampleRate;
+		if(sampleRate<This.sampleRate){
+			CLog(msg+",数据已丢弃",3);
+			if(!This.pcmTotal){
+				This.sendAbortMsg=msg;
+			};
+			This._send();
+			return;
+		};
+		if(This.sendAbortMsg==msg){
+			This.sendAbortMsg="";
+		};
+		
+		if(buffersOffset){
+			var newBuffers=[];
+			for(var idx=buffersOffset;idx<buffers.length;idx++){
+				newBuffers.push(buffers[idx]);
+			};
+			buffers=newBuffers;
+		};
+		
+		var pcm=Recorder.SampleData(buffers,sampleRate,This.sampleRate).data;
+		This.pcmTotal+=pcm.length;
+		This.pcmBuffers.push(pcm);
+		This._send();
+	}
+	,_send:function(){
+		var This=this,set=This.set;
+		if(This.sendWait){
+			//阻塞中
+			return;
+		};
+		var tryStopEnd=function(){
+			This.stopWait&&This.stopWait();
+		};
+		if(This.state==2 && (!This.started || !This.stopWait)){
+			//已经stop了,并且未ok开始 或者 未在等待结果
+			tryStopEnd();
+			return;
+		};
+		if(This.sendAbort){
+			//已异常中断了
+			tryStopEnd();
+			return;
+		};
+		
+		//异常提前终止
+		var abort=function(err){
+			if(!This.sendAbort){
+				This.sendAbort=1;
+				This.sendAbortMsg=err||"-";
+				processCall(0,1);//abort后只调用最后一次
+			};
+			This._send();
+		};
+		var processCall=function(addSize,abortLast){
+			if(!abortLast && This.sendAbort){
+				return false;
+			};
+			addSize=addSize||0;
+			if(!set.asrProcess){
+				//默认超过1分钟自动停止
+				return This.sendTotal+addSize<=size60s;
+			};
+			//实时回调
+			var val=set.asrProcess(This.getText()
+				,This.sendDuration(addSize)
+				,This.sendAbort?This.sendAbortMsg:"");
+			if(!This._prsw && typeof(val)!="boolean"){
+				CLog("asrProcess返回值必须是boolean类型,true才能继续识别,否则立即超时",1);
+			};
+			This._prsw=1;
+			return val;
+		};
+		var size5s=This.sampleRate*5;
+		var size60s=This.sampleRate*60;
+		
+		//建立ws连接
+		var ws=This.wsCur;
+		if(!ws){
+			if(This.started){//已start才创建ws
+				var resTxt={};
+				This.resTxts.push(resTxt);
+				ws=This.wsCur=This._wsNew(
+					This.tokenData
+					,"ws:"+This.resTxts.length
+					,resTxt
+					,function(){
+						processCall();
+					}
+					,function(){
+						This._send();
+					}
+					,function(err){
+						//异常中断
+						if(ws==This.wsCur){
+							abort(err);
+						};
+					}
+				);
+			};
+			return;
+		};
+		
+		//正在新建新1分钟连接,等着
+		if(This.wsLock){
+			return;
+		};
+		//已有ok的连接,直接陆续将所有缓冲分段发送完
+		if(ws._s!=2 || ws.isStop){
+			//正在关闭或者其他状态不管,等着
+			return;
+		};
+		//没有数据了
+		if(This.pcmSend>=This.pcmTotal){
+			if(This.state==1){
+				//缓冲数据已发送完,等待新数据
+				return;
+			};
+			
+			//已stop,结束识别得到最终结果
+			ws.stopWs(function(){
+				tryStopEnd();
+			},function(err){
+				abort(err);
+			});
+			return;
+		};
+		
+		//准备本次发送数据块
+		var minSize=This.sampleRate/1000*50;//最小发送量50ms ≈1.6k
+		var maxSize=This.sampleRate;//最大发送量1000ms ≈32k
+		//速度控制1,取决于网速
+		if((ws.bufferedAmount||0)/2>maxSize*3){
+			//传输太慢,阻塞一会再发送
+			This.sendWait=setTimeout(function(){
+				This.sendWait=0;
+				This._send();
+			},100);
+			return;
+		};
+		//速度控制2,取决于已发送时长,单个文件才会被控制速率
+		if(This.sendSpeed){
+			var spMaxMs=(Date.now()-ws.okTime)*This.sendSpeed;
+			var nextMs=(This.sendCurSize+maxSize/3)/This.sampleRate*1000;
+			var delay=Math.floor((nextMs-spMaxMs)/This.sendSpeed);
+			if(delay>0){
+				//传输太快,怕底层识别不过来,降低发送速度
+				CLog("[ASR]延迟"+delay+"ms发送");
+				This.sendWait=setTimeout(function(){
+					This.sendWait=0;
+					This._send();
+				},delay);
+				return;
+			};
+		};
+		
+		var needSend=1;
+		var copyBuffers=function(offset,buffers,dist){
+			var size=dist.length;
+			for(var i=0,idx=0;idx<size&&i<buffers.length;){
+				var pcm=buffers[i];
+				if(pcm.length-offset<=size-idx){
+					dist.set(offset==0?pcm:pcm.subarray(offset),idx);
+					idx+=pcm.length-offset;
+					offset=0;
+					buffers.splice(i,1);
+				}else{
+					dist.set(pcm.subarray(offset,offset+(size-idx)),idx);
+					offset+=size-idx;
+					break;
+				};
+			};
+			return offset;
+		};
+		if(This.joinIsOpen){
+			//发送新1分钟的开头重叠5秒数据
+			if(This.joinOffset==-1){
+				//精准定位5秒
+				This.joinSend=0;
+				This.joinOffset=0;
+				This.log("发送上1分钟结尾5秒数据...");
+				var total=0;
+				for(var i=This.joinBuffers.length-1;i>=0;i--){
+					total+=This.joinBuffers[i].length;
+					if(total>=size5s){
+						This.joinBuffers.splice(0, i);
+						This.joinSize=total;
+						This.joinOffset=total-size5s;
+						break;
+					};
+				};
+			};
+			var buffersSize=This.joinSize-This.joinOffset;//缓冲余量
+			var size=Math.min(maxSize,buffersSize);
+			if(size<=0){
+				//重叠5秒数据发送完毕
+				This.log("发送新1分钟数据(重叠"+Math.round(This.joinSend/This.sampleRate*1000)+"ms)...");
+				This.joinBuffers=[];
+				This.joinSize=0;
+				This.joinOffset=-1;
+				This.joinIsOpen=0;
+				This._send();
+				return;
+			};
+			
+			//创建块数据,消耗掉buffers
+			var chunk=new Int16Array(size);
+			This.joinSend+=size;
+			This.joinSendTotal+=size;
+			This.joinOffset=copyBuffers(This.joinOffset,This.joinBuffers,chunk);
+			
+			This.joinSize=0;
+			for(var i=0;i<This.joinBuffers.length;i++){
+				This.joinSize+=This.joinBuffers[i].length;
+			};
+		}else{
+			var buffersSize=This.pcmTotal-This.pcmSend;//缓冲余量
+			var buffersDur=Math.round(buffersSize/This.sampleRate*1000);
+			var curHasSize=size60s-This.sendCurSize;//当前连接剩余能发送的量
+			var sizeNext=Math.min(maxSize,buffersSize);//不管连接剩余数时本应当发送的数量
+			var size=Math.min(sizeNext,curHasSize);
+			if(This.state==1 && size<Math.min(minSize,curHasSize)){
+				//不够发送一次的,等待新数据
+				return;
+			};
+			var needNew=0;
+			if(curHasSize<=0){
+				//当前连接一分钟已消耗完
+				if(This.state==2 && buffersSize<This.sampleRate*1.2){
+					//剩余的量太少,并且已stop,没必要再新建连接,直接丢弃
+					size=buffersSize;
+					This.log("丢弃结尾"+buffersDur+"ms数据","#999");
+					needSend=0;
+				}else{
+					//开始新1分钟的连接,等到实时回调后再看要不要新建
+					needNew=true;
+				};
+			};
+			//回调看看是否要超时终止掉
+			if(needSend && !processCall(sizeNext)){//用本应当的发送量来计算
+				//超时,终止识别
+				var durS=Math.round(This.asrDuration()/1000);
+				This.log("已主动超时,共识别"+durS+"秒,丢弃缓冲"+buffersDur+"ms,正在终止...");
+				This.wsLock=1;//阻塞住后续调用
+				ws.stopWs(function(){
+					abort("已主动超时,共识别"+durS+"秒,终止识别");
+				},function(err){
+					abort(err);
+				});
+				return;
+			};
+			//开始新1分钟的连接
+			if(needNew){
+				CLog("[ASR]新1分钟接续,当前缓冲"+buffersDur+"ms...");
+				This.wsLock=1;//阻塞住后续调用
+				ws.stopWs(function(){
+					This._token(function(){
+						This.log("新1分钟接续OK,当前缓冲"+buffersDur+"ms",2);
+						This.wsLock=0;
+						This.wsCur=0;//重置当前连接
+						This.sendCurSize=0;
+						
+						This.joinIsOpen=1;//新1分钟先发重叠的5秒数据
+						This.joinOffset=-1;
+						
+						This._send();
+					},function(err){
+						abort("语音识别新1分钟token接口出错:"+err);
+					});
+				},function(err){
+					abort(err);
+				});
+				return;
+			};
+			
+			//创建块数据,消耗掉buffers
+			var chunk=new Int16Array(size);
+			This.pcmOffset=copyBuffers(This.pcmOffset,This.pcmBuffers,chunk);
+			This.pcmSend+=size;
+			
+			//写入到下一分钟的头5秒重叠区域中,不管写了多少,写就完了
+			This.joinBuffers.push(chunk);
+			This.joinSize+=size;
+		};
+		
+		This.sendCurSize+=chunk.length;
+		This.sendTotal+=chunk.length;
+		if(needSend){
+			try{
+				ws.send(chunk.buffer);
+			}catch(e){CLog("ws.send",1,e);};
+		};
+		
+		//不要停
+		This.sendWait=setTimeout(function(){
+			This.sendWait=0;
+			This._send();
+		});//仅退出调用堆栈
+	}
+	
+	
+	
+	/**返回实时结果文本,如果已stop返回的就是最终文本**/
+	,getText:function(){
+		var arr=this.resTxts;
+		var txt="";
+		for(var i=0;i<arr.length;i++){
+			var obj=arr[i];
+			if(obj.fullTxt){
+				txt=obj.fullTxt;
+			}else{
+				var tmp=obj.tempTxt||"";
+				if(obj.okTxt){
+					tmp=obj.okTxt;
+				};
+				//5秒重叠进行模糊拼接
+				if(!txt){
+					txt=tmp;
+				}else{
+					var left=txt.substr(-20);//240字/分
+					var finds=[];
+					for(var x=0,max=Math.min(17,tmp.length-3);x<=max;x++){
+						for(var i0=0;i0<17;i0++){
+							if(left[i0]==tmp[x]){
+								var n=1;
+								for(;n<17;n++){
+									if(left[i0+n]!=tmp[x+n]){
+										break;
+									};
+								};
+								if(n>=3){//3字相同即匹配
+									finds.push({x:x,i0:i0,n:n});
+								};
+							};
+						};
+					};
+					finds.sort(function(a,b){
+						var v=b.n-a.n;
+						return v!=0?v:b.i0-a.i0;//越长越好,越靠后越好
+					});
+					var f0=finds[0];
+					if(f0){
+						txt=txt.substr(0,txt.length-left.length+f0.i0);
+						txt+=tmp.substr(f0.x);
+					}else{
+						txt+=tmp;
+					};
+				};
+				//存起来
+				if(obj.okTxt!=null && tmp==obj.okTxt){
+					obj.fullTxt=txt;
+				};
+			};
+		};
+		return txt;
+	}
+	
+	//创建新的wss连接
+	,_wsNew:function(sData,id,resTxt,process,connOk,connFail){
+		var uuid=function(){
+			var s=[];
+			for(var i=0,r;i<32;i++){
+				r=Math.floor(Math.random()*16);
+				s.push(String.fromCharCode(r<10?r+48:r-10+97));
+			};
+			return s.join("");
+		};
+		var This=this,set=This.set;
+		CLog("[ASR "+id+"]正在连接...");
+		var url="wss://nls-gateway.cn-shanghai.aliyuncs.com/ws/v1?token="+sData.token;
+		if(set.compatibleWebSocket){
+			var ws=set.compatibleWebSocket(url);
+		}else{
+			var ws=new WebSocket(url);
+		}
+		
+		//ws._s=0 0连接中 1opening 2openOK 3stoping 4closeing -1closed
+		//ws.isStop=0 1已停止识别
+		ws.onclose=function(){
+			if(ws._s==-1)return;
+			var isFail=ws._s!=4;
+			ws._s=-1;
+			This.log("["+id+"]close");
+			
+			isFail&&connFail(ws._err||"连接"+id+"已关闭");
+		};
+		ws.onerror=function(e){
+			if(ws._s==-1)return;
+			var msg="网络连接错误";
+			ws._err||(ws._err=msg);
+			This.log("["+id+"]"+msg,1);
+			ws.onclose();
+		};
+		ws.onopen=function(){
+			if(ws._s==-1)return;
+			ws._s=1;
+			CLog("[ASR "+id+"]open");
+			ws._task=uuid();
+			ws.send(JSON.stringify({
+				header:{
+					message_id:uuid()
+					,task_id:ws._task
+					,appkey:sData.appkey
+					
+					,namespace:"SpeechRecognizer"
+					,name:"StartRecognition"
+				}
+				,payload:{
+					format:"pcm"
+					,sample_rate:This.sampleRate
+					,enable_intermediate_result:true //返回中间识别结果
+					,enable_punctuation_prediction:true //添加标点
+					,enable_inverse_text_normalization:true //后处理中将数值处理
+				}
+				,context:{ }
+			}));
+		};
+		ws.onmessage=function(e){
+			var data=e.data;
+			var logMsg=true;
+			if(typeof(data)=="string" && data[0]=="{"){
+				data=JSON.parse(data);
+				var header=data.header||{};
+				var payload=data.payload||{};
+				var name=header.name||"";
+				var status=header.status||0;
+				
+				var isFail=name=="TaskFailed";
+				var errMsg="";
+				
+				//init
+				if(ws._s==1 && (name=="RecognitionStarted" || isFail)){
+					if(isFail){
+						errMsg="连接"+id+"失败["+status+"]"+header.status_text;
+					}else{
+						ws._s=2;
+						This.log("["+id+"]连接OK");
+						ws.okTime=Date.now();
+						connOk();
+					};
+				};
+				//中间结果
+				if(ws._s==2 && (name=="RecognitionResultChanged" || isFail)){
+					if(isFail){
+						errMsg="识别出现错误["+status+"]"+header.status_text;
+					}else{
+						logMsg=!ws._clmsg;
+						ws._clmsg=1;
+						resTxt.tempTxt=payload.result||"";
+						process();
+					};
+				};
+				//stop
+				if(ws._s==3 && (name=="RecognitionCompleted" || isFail)){
+					var txt="";
+					if(isFail){
+						errMsg="停止识别出现错误["+status+"]"+header.status_text;
+					}else{
+						txt=payload.result||"";
+						This.log("["+id+"]最终识别结果:"+txt);
+					};
+					ws.stopCall&&ws.stopCall(txt,errMsg);
+				};
+				
+				if(errMsg){
+					This.log("["+id+"]"+errMsg,1);
+					ws._err||(ws._err=errMsg);
+				};
+			};
+			if(logMsg){
+				CLog("[ASR "+id+"]msg",data);
+			};
+		};
+		ws.stopWs=function(True,False){
+			if(ws._s!=2){
+				False(id+"状态不正确["+ws._s+"]");
+				return;
+			};
+			ws._s=3;
+			ws.isStop=1;
+			
+			ws.stopCall=function(txt,err){
+				clearTimeout(ws.stopInt);
+				ws.stopCall=0;
+				ws._s=4;
+				ws.close();
+				
+				resTxt.okTxt=txt;
+				process();
+				
+				if(err){
+					False(err);
+				}else{
+					True();
+				};
+			};
+			ws.stopInt=setTimeout(function(){
+				ws.stopCall&&ws.stopCall("","停止识别返回结果超时");
+			},10000);
+			
+			CLog("[ASR "+id+"]send stop");
+			ws.send(JSON.stringify({
+				header:{
+					message_id:uuid()
+					,task_id:ws._task
+					,appkey:sData.appkey
+					
+					,namespace:"SpeechRecognizer"
+					,name:"StopRecognition"
+				}
+			}));
+		};
+		if(ws.connect)ws.connect(); //兼容时会有这个方法
+		return ws;
+	}
+	
+	
+	
+	//获得开始识别的token信息
+	,_token:function(True,False){
+		var This=this,set=This.set;
+		if(!set.tokenApi){
+			False("未配置tokenApi");return;
+		};
+		
+		(set.apiRequest||DefaultPost)(set.tokenApi,set.apiArgs||{},function(data){
+			if(!data || !data.appkey || !data.token){
+				False("apiRequest回调的数据格式不正确");return;
+			};
+			This.tokenData=data;
+			True();
+		},False);
+	}
+	
+};
+
+
+
+//手撸一个ajax
+function DefaultPost(url,args,success,fail){
+	var xhr=new XMLHttpRequest();
+	xhr.timeout=20000;
+	xhr.open("POST",url);
+	xhr.onreadystatechange=function(){
+		if(xhr.readyState==4){
+			if(xhr.status==200){
+				try{
+					var o=JSON.parse(xhr.responseText);
+				}catch(e){};
+				
+				if(o.c!==0 || !o.v){
+					fail(o.m||"接口返回非预定义json数据");
+					return;
+				};
+				success(o.v);
+			}else{
+				fail("请求失败["+xhr.status+"]");
+			}
+		}
+	};
+	var arr=[];
+	for(var k in args){
+		arr.push(k+"="+encodeURIComponent(args[k]));
+	};
+	xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded");
+	xhr.send(arr.join("&"));
+};
+
+function NOOP(){};
+
+Recorder[ASR_Aliyun_ShortTxt]=ASR_Aliyun_Short;
+
+	
+}));

+ 860 - 0
src/extensions/buffer_stream.player.js

@@ -0,0 +1,860 @@
+/*
+录音 Recorder扩展,实时播放录音片段文件,把片段文件转换成MediaStream流
+
+https://github.com/xiangyuecn/Recorder
+
+BufferStreamPlayer可以通过input方法一次性输入整个音频文件,或者实时输入音频片段文件,然后播放出来;输入支持格式:pcm、wav、mp3等浏览器支持的音频格式,非pcm格式会自动解码成pcm(播放音质效果比pcm、wav格式差点);输入前输入后都可进行处理要播放的音频,比如:混音、变速、变调;输入的音频会写入到内部的MediaStream流中,完成将连续的音频片段文件转换成流。
+
+BufferStreamPlayer可以用于:
+	1. Recorder onProcess等实时处理中,将实时处理好的音频片段转直接换成MediaStream,此流可以作为WebRTC的local流发送到对方,或播放出来;
+	2. 接收到的音频片段文件的实时播放,比如:WebSocket接收到的录音片段文件播放、WebRTC remote流(Recorder支持对这种流进行实时处理)实时处理后的播放;
+	3. 单个音频文件的实时播放处理,比如:播放一段音频,并同时进行可视化绘制(其实自己解码+播放绘制比直接调用这个更有趣,但这个省事、配套功能多点)。
+
+在线测试例子:
+	https://xiangyuecn.gitee.io/recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.decode_buffer_stream_player
+调用示例:
+	var stream=Recorder.BufferStreamPlayer(set)
+	//创建好后第一件事就是start打开流,打开后就会开始播放input输入的音频,set具体配置看下面源码;注意:start需要在用户操作(触摸、点击等)时进行调用,原因参考runningContext配置
+	stream.start(()=>{
+		stream.currentTime;//当前已播放的时长,单位ms,数值变化时会有onUpdateTime事件
+		stream.duration;//已输入的全部数据总时长,单位ms,数值变化时会有onUpdateTime事件;实时模式下意义不大,会比实际播放的长,因为实时播放时卡了就会丢弃部分数据不播放
+		stream.isStop;//是否已停止,调用了stop方法时会设为true
+		stream.isPause;//是否已暂停,调用了pause方法时会设为true
+		stream.isPlayEnd;//已输入的数据是否播放到了结尾(没有可播放的数据了),input后又会变成false;可代表正在缓冲中或播放结束,状态变更时会有onPlayEnd事件
+		
+		//如果不要默认的播放,可以设置set.play为false,这种情况下只拿到MediaStream来用
+		stream.getMediaStream() //通过getMediaStream方法得到MediaStream流,此流可以作为WebRTC的local流发送到对方,或者直接拿来赋值给audio.srcObject来播放(和赋值audio.src作用一致);未start时调用此方法将会抛异常
+		
+		stream.getAudioSrc() //【已过时】超低版本浏览器中得到MediaStream流的字符串播放地址,可赋值给audio标签的src,直接播放音频;未start时调用此方法将会抛异常;新版本浏览器已停止支持将MediaStream转换成url字符串,调用本方法新浏览器会抛异常,因此在不需要兼容不支持srcObject的超低版本浏览器时,请直接使用getMediaStream然后赋值给auido.srcObject来播放
+	},(errMsg)=>{
+		//start失败,无法播放
+	});
+
+	//随时都能调用input,会等到start成功后播放出来,不停的调用input,就能持续的播放出声音了,需要暂停播放就不要调用input就行了
+	stream.input(anyData); //anyData数据格式 和更多说明,请阅读下面的input方法源码注释
+	
+	//暂停播放,暂停后:实时模式下会丢弃所有input输入的数据(resume时只播放新input的数据),非实时模式下所有input输入的数据会保留到resume时继续播放
+	stream.pause();
+	//恢复播放,实时模式下只会从最新input的数据开始播放,非实时模式下会从暂停的位置继续播放
+	stream.resume();
+
+	//不要播放了就调用stop停止播放,关闭所有资源
+	stream.stop();
+	
+
+
+注意:已知Firefox的AudioBuffer没法动态修改数据,所以对于带有这种特性的浏览器将采用先缓冲后再播放(类似assets/runtime-codes/fragment.playbuffer.js),音质会相对差一点;其他浏览器测试Android、IOS、Chrome无此问题;start方法中有一大段代码给浏览器做了特性检测并进行兼容处理。
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var BufferStreamPlayer=function(set){
+	return new fn(set);
+};
+var BufferStreamPlayerTxt="BufferStreamPlayer";
+var fn=function(set){
+	var This=this;
+	var o={
+		play:true //要播放声音,设为false不播放,只提供MediaStream
+		,realtime:true /*默认为true实时模式,设为false为非实时模式
+			实时模式:设为 true 或 {maxDelay:300,discardAll:false}配置对象
+				如果有新的input输入数据,但之前输入的数据还未播放完的时长不超过maxDelay时(缓冲播放延迟默认限制在300ms内),如果积压的数据量过大则积压的数据将会被直接丢弃,少量积压会和新数据一起加速播放,最终达到尽快播放新输入的数据的目的;这在网络不流畅卡顿时会发挥很大作用,可有效降低播放延迟;出现加速播放时声音听起来会比较怪异,可配置discardAll=true来关闭此特性,少量积压的数据也直接丢弃,不会加速播放;如果你的音频数据块超过200ms,需要调大maxDelay(取值100-800ms)
+			非实时模式:设为 false
+				连续完整的播放完所有input输入的数据,之前输入的还未播放完又有新input输入会加入队列排队播放,比如用于:一次性同时输入几段音频完整播放
+			*/
+				
+		
+		//,onInputError:fn(errMsg, inputIndex) //当input输入出错时回调,参数为input第几次调用和错误消息
+		//,onUpdateTime:fn() //已播放时长、总时长更新回调(stop、pause、resume后一定会回调),this.currentTime为已播放时长,this.duration为已输入的全部数据总时长(实时模式下意义不大,会比实际播放的长),单位都是ms
+		//,onPlayEnd:fn() //没有可播放的数据时回调(stop后一定会回调),已输入的数据已全部播放完了,可代表正在缓冲中或播放结束;之后如果继续input输入了新数据,播放完后会再次回调,因此会多次回调;非实时模式一次性输入了数据时,此回调相当于播放完成,可以stop掉,重新创建对象来input数据可达到循环播放效果
+		
+		//,decode:false //input输入的数据在调用transform之前是否要进行一次音频解码成pcm [Int16,...]
+			//mp3、wav等都可以设为true、或设为{fadeInOut:true}配置对象,会自动解码成pcm;默认会开启fadeInOut对解码的pcm首尾进行淡入淡出处理,减少爆音(wav等解码后和原始pcm一致的音频,可以把fadeInOut设为false)
+		
+		//transform:fn(inputData,sampleRate,True,False)
+			//将input输入的data(如果开启了decode将是解码后的pcm)转换处理成要播放的pcm数据;如果没有解码也没有提供本方法,input的data必须是[Int16,...]并且设置set.sampleRate
+			//inputData:any input方法输入的任意格式数据,只要这个转换函数支持处理;如果开启了decode,此数据为input输入的数据解码后的pcm [Int16,...]
+			//sampleRate:123 如果设置了decode为解码后的采样率,否则为set.sampleRate || null
+			//True(pcm,sampleRate) 回调处理好的pcm数据([Int16,...])和pcm的采样率
+			//False(errMsg) 处理失败回调
+			
+		//sampleRate:16000 //可选input输入的数据默认的采样率,当没有设置解码也没有提供transform时应当明确设置采样率
+		
+		//runningContext:AudioContext //可选提供一个state为running状态的AudioContext对象(ctx),默认会在start时自动创建一个新的ctx,这个配置的作用请参阅Recorder的runningContext配置
+	};
+	for(var k in set){
+		o[k]=set[k];
+	};
+	This.set=set=o;
+	
+	if(!set.onInputError){
+		set.onInputError=function(err,n){ CLog(err,1); };
+	}
+};
+fn.prototype=BufferStreamPlayer.prototype={
+	/**【已过时】获取MediaStream的audio播放地址,新版浏览器、未start将会抛异常**/
+	getAudioSrc:function(){
+		CLog($T("0XYC::getAudioSrc方法已过时:请直接使用getMediaStream然后赋值给audio.srcObject,仅允许在不支持srcObject的浏览器中调用本方法赋值给audio.src以做兼容"),3);
+		if(!this._src){
+			//新版chrome调用createObjectURL会直接抛异常了 https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL#using_object_urls_for_media_streams
+			this._src=(window.URL||webkitURL).createObjectURL(this.getMediaStream());
+		}
+		return this._src;
+	}
+	/**获取MediaStream流对象,未start将会抛异常**/
+	,getMediaStream:function(){
+		if(!this._dest){
+			throw new Error(NoStartMsg());
+		}
+		return this._dest.stream;
+	}
+	
+	
+	/**打开音频流,打开后就会开始播放input输入的音频;注意:start需要在用户操作(触摸、点击等)时进行调用,原因参考runningContext配置
+	 * True() 打开成功回调
+	 * False(errMsg) 打开失败回调**/
+	,start:function(True,False){
+		var falseCall=function(msg,noClear){
+			var next=!checkStop();
+			if(!noClear)This._clear();
+			CLog(msg,1);
+			next&&False&&False(msg);
+		};
+		var checkStop=function(){
+			if(This.isStop){
+				CLog($T("6DDt::start被stop终止"),3);
+				return true;
+			};
+		};
+		var This=this,set=This.set,__abTest=This.__abTest;
+		if(This._Tc!=null){
+			falseCall($T("I4h4::{1}多次start",0,BufferStreamPlayerTxt),1);
+			return;
+		}
+		if(!isBrowser){
+			falseCall($T.G("NonBrowser-1",[BufferStreamPlayerTxt]));
+			return;
+		}
+		This._Tc=0;//currentTime 对应的采样数
+		This._Td=0;//duration 对应的采样数
+		
+		This.currentTime=0;//当前已播放的时长,单位ms
+		This.duration=0;//已输入的全部数据总时长,单位ms;实时模式下意义不大,会比实际播放的长,因为实时播放时卡了就会丢弃部分数据不播放
+		This.isStop=0;//是否已停止
+		This.isPause=0;//是否已暂停
+		This.isPlayEnd=0;//已输入的数据是否播放到了结尾(没有可播放的数据了),input后又会变成false;可代表正在缓冲中或播放结束
+		
+		This.inputN=0;//第n次调用input
+		
+		This.inputQueueIdx=0;//input调用队列当前已处理到的位置
+		This.inputQueue=[];//input调用队列,用于纠正执行顺序
+		
+		This.bufferSampleRate=0;//audioBuffer的采样率,首次input后就会固定下来
+		This.audioBuffer=0;
+		This.pcmBuffer=[[],[]];//未推入audioBuffer的pcm数据缓冲
+		
+		var fail=function(msg){
+			falseCall($T("P6Gs::浏览器不支持打开{1}",0,BufferStreamPlayerTxt)+(msg?": "+msg:""));
+		};
+		
+		var ctx=set.runningContext || Recorder.GetContext(true); This._ctx=ctx;
+		var sVal=ctx.state,spEnd=Recorder.CtxSpEnd(sVal);
+		!__abTest&&CLog("start... ctx.state="+sVal+(
+			spEnd?$T("JwDm::(注意:ctx不是running状态,start需要在用户操作(触摸、点击等)时进行调用,否则会尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)"):""
+		));
+		
+		var support=1;
+		if(!ctx || !ctx.createMediaStreamDestination){
+			support=0;
+		}else{
+			var source=ctx.createBufferSource();
+			if(!source.start || source.onended===undefined){
+				support=0;//createBufferSource版本太低,难兼容
+			}
+		};
+		if(!support){
+			fail("");
+			return;
+		};
+		
+		
+		var end=function(){
+			if(checkStop())return;
+			//创建MediaStream
+			var dest=ctx.createMediaStreamDestination();
+			dest.channelCount=1;
+			This._dest=dest;
+			
+			!__abTest&&CLog("start ok");
+			True&&True();
+			
+			This._inputProcess();//处理未完成start前的input调用
+			This._updateTime();//更新时间
+			
+			//定时在没有input输入时,将未写入buffer的数据写进去
+			if(!badAB){
+				This._writeInt=setInterval(function(){
+					This._writeBuffer();
+				},100);
+			}else{
+				CLog($T("qx6X::此浏览器的AudioBuffer实现不支持动态特性,采用兼容模式"),3);
+				This._writeInt=setInterval(function(){
+					This._writeBad();
+				},10);//定时调用进行数据写入播放
+			}
+		};
+		var abTest=function(){
+			//浏览器实现检测,已知Firefox的AudioBuffer没法在_writeBuffer中动态修改数据;检测方法:直接新开一个,输入一段测试数据,看看能不能拿到流中的数据
+			var testStream=BufferStreamPlayer({ play:false,sampleRate:8000,runningContext:ctx });
+			testStream.__abTest=1; var testRec;
+			testStream.start(function(){
+				testRec=Recorder({
+					type:"unknown"
+					,sourceStream:testStream.getMediaStream()
+					,runningContext:ctx
+					,onProcess:function(buffers){	
+						var bf=buffers[buffers.length-1],all0=1;
+						for(var i=0;i<bf.length;i++){
+							if(bf[i]!=0){ all0=0; break; }
+						}
+						if(all0 && buffers.length<5){
+							return;//再等等看,最长约等500ms
+						}
+						testRec.close();
+						testStream.stop();
+						
+						if(testInt){ clearTimeout(testInt); testInt=0;
+							//全部是0就是浏览器不行,要缓冲一次性播放进行兼容
+							badAB=all0;
+							BufferStreamPlayer.BadAudioBuffer=badAB;
+							end();
+						}
+					}
+				});
+				testRec.open(function(){
+					testRec.start();
+				},function(msg){
+					testStream.stop(); fail(msg);
+				});
+			},fail);
+			//超时没有回调
+			var testInt=setTimeout(function(){
+				testInt=0; testStream.stop(); testRec&&testRec.close();
+				fail($T("cdOx::环境检测超时"));
+			},1500);
+			//随机生成1秒的数据,rec有一次回调即可
+			var data=new Int16Array(8000);
+			for(var i=0;i<8000;i++){
+				data[i]=~~(Math.random()*0x7fff*2-0x7fff);
+			}
+			testStream.input(data);
+		};
+		
+		var badAB=BufferStreamPlayer.BadAudioBuffer;
+		var ctxNext=function(){
+			if(__abTest || badAB!=null){
+				setTimeout(end); //应当setTimeout一下强转成异步,统一调用代码时的行为
+			}else{
+				abTest();
+			};
+		};
+		var tag="AudioContext resume: ";
+		Recorder.ResumeCtx(ctx,function(runC){
+			runC&&CLog(tag+"wait...");
+			return !This.isStop;
+		},function(runC){
+			runC&&CLog(tag+ctx.state);
+			ctxNext();
+		},function(err){ //比较少见,可能没有影响
+			CLog(tag+ctx.state+" "+$T("S2Bu::可能无法播放:{1}",0,err),1);
+			ctxNext();
+		});
+	}
+	,_clear:function(){
+		var This=this;
+		This.isStop=1;
+		clearInterval(This._writeInt);
+		This.inputQueue=0;
+		
+		if(This._src){
+			(window.URL||webkitURL).revokeObjectURL(This._src);
+			This._src=0;
+		}
+		if(This._dest){
+			Recorder.StopS_(This._dest.stream);
+			This._dest=0;
+		}
+		if(!This.set.runningContext && This._ctx){
+			Recorder.CloseNewCtx(This._ctx);
+		}
+		This._ctx=0;
+		
+		var source=This.bufferSource;
+		if(source){
+			source.disconnect();
+			source.stop();
+		}
+		This.bufferSource=0;
+		This.audioBuffer=0;
+	}
+	/**停止播放,关闭所有资源**/
+	,stop:function(){
+		var This=this;
+		This._clear();
+		
+		!This.__abTest&&CLog("stop");
+		This._playEnd(1);
+	}
+	/**暂停播放,暂停后:实时模式下会丢弃所有input输入的数据(resume时只播放新input的数据),非实时模式下所有input输入的数据会保留到resume时继续播放**/
+	,pause:function(){
+		CLog("pause");
+		this.isPause=1;
+		this._updateTime(1);
+	}
+	/**恢复播放,实时模式下只会从最新input的数据开始播放,非实时模式下会从暂停的位置继续播放**/
+	,resume:function(){
+		var This=this,tag="resume",tag3=tag+"(wait ctx)";
+		CLog(tag);
+		This.isPause=0;
+		This._updateTime(1);
+		
+		var ctx=This._ctx;
+		if(ctx){ //AudioContext如果被暂停,尽量恢复
+			Recorder.ResumeCtx(ctx,function(runC){
+				runC&&CLog(tag3+"...");
+				return !This.isStop && !This.isPause;
+			},function(runC){
+				runC&&CLog(tag3+ctx.state);
+			},function(err){
+				CLog(tag3+ctx.state+"[err]"+err,1);
+			});
+		};
+	}
+	
+	
+	//当前输入的数据播放到结尾时触发回调,stop时永远会触发回调
+	,_playEnd:function(stop){
+		var This=this,startTime=This._PNs,call=This.set.onPlayEnd;
+		if(stop || !This.isPause){//暂停播到结尾不算
+			if(stop || !This.isPlayEnd){
+				if(stop || (startTime && Date.now()-startTime>500)){//已停止或者延迟确认成功
+					This._PNs=0;
+					This.isPlayEnd=1;
+					call&&call();
+					This._updateTime(1);
+				}else if(!startTime){//刚检测到的没有数据了,开始延迟确认
+					This._PNs=Date.now();
+				};
+			};
+		};
+	}
+	//有数据播放时,取消已到结尾状态
+	,_playLive:function(){
+		var This=this;
+		This.isPlayEnd=0;
+		This._PNs=0;
+	}
+	//时间更新时触发回调,没有更新时不会触发回调
+	,_updateTime:function(must){
+		var This=this,sampleRate=This.bufferSampleRate||9e9,call=This.set.onUpdateTime;
+		This.currentTime=Math.round(This._Tc/sampleRate*1000);
+		This.duration=Math.round(This._Td/sampleRate*1000);
+		
+		var s=""+This.currentTime+This.duration;
+		if(must || This._UTs!=s){
+			This._UTs=s;
+			call&&call();
+		}
+	}
+	
+	
+	
+	
+	
+	/**输入任意格式的音频数据,未完成start前调用会等到start成功后生效
+		anyData: any 具体类型取决于:
+			set.decode为false时:
+				未提供set.transform,数据必须是pcm[Int16,...],此时的set必须提供sampleRate;
+				提供了set.transform,数据为transform方法支持的任意格式。
+			set.decode为true时:
+				数据必须是ArrayBuffer,会自动解码成pcm[Int16,...];注意输入的每一片数据都应该是完整的一个音频片段文件,否则可能会解码失败;注意ArrayBuffer对象是Transferable object,参与解码后此对象将不可用,因为内存数据已被转移到了解码线程,可通过 stream.input(arrayBuffer.slice(0)) 形式复制一份再解码就没有这个问题了。
+				
+		关于anyData的二进制长度:
+			如果是提供的pcm、wav格式数据,数据长度对播放无太大影响,很短的数据也能很好的连续播放。
+			如果是提供的mp3这种必须解码才能获得pcm的数据,数据应当尽量长点,测试发现片段有300ms以上解码后能很好的连续播放,低于100ms解码后可能会有明显的杂音,更低的可能会解码失败;当片段确实太小时,可以将本来会多次input调用的数据缓冲起来,等数据量达到了300ms再来调用一次input,能比较显著的改善播放音质。
+	 **/
+	,input:function(anyData){
+		var This=this,set=This.set;
+		var inputN=++This.inputN;
+		if(!This.inputQueue){
+			throw new Error(NoStartMsg());
+		}
+		
+		var decSet=set.decode;
+		if(decSet){
+			//先解码
+			DecodeAudio(anyData, function(data){
+				if(!This.inputQueue)return;//stop了
+				if(decSet.fadeInOut==null || decSet.fadeInOut){
+					FadeInOut(data.data, data.sampleRate);//解码后的数据进行一下淡入淡出处理,减少爆音
+				}
+				This._input2(inputN, data.data, data.sampleRate);
+			},function(err){
+				This._inputErr(err, inputN);
+			});
+		}else{
+			This._input2(inputN, anyData, set.sampleRate);
+		}
+	}
+	//transform处理
+	,_input2:function(inputN, anyData, sampleRate){
+		var This=this,set=This.set;
+		
+		if(set.transform){
+			set.transform(anyData, sampleRate, function(pcm, sampleRate2){
+				if(!This.inputQueue)return;//stop了
+				
+				sampleRate=sampleRate2||sampleRate;
+				This._input3(inputN, pcm, sampleRate);
+			},function(err){
+				This._inputErr(err, inputN);
+			});
+		}else{
+			This._input3(inputN, anyData, sampleRate);
+		}
+	}
+	//转换好的pcm加入input队列,纠正调用顺序,未start时等待
+	,_input3:function(inputN, pcm, sampleRate){
+		var This=this;
+		
+		if(!pcm || !pcm.subarray){
+			This._inputErr($T("ZfGG::input调用失败:非pcm[Int16,...]输入时,必须解码或者使用transform转换"), inputN);
+			return;
+		}
+		if(!sampleRate){
+			This._inputErr($T("N4ke::input调用失败:未提供sampleRate"), inputN);
+			return;
+		}
+		if(This.bufferSampleRate && This.bufferSampleRate!=sampleRate){
+			This._inputErr($T("IHZd::input调用失败:data的sampleRate={1}和之前的={2}不同",0,sampleRate,This.bufferSampleRate), inputN);
+			return;
+		}
+		if(!This.bufferSampleRate){
+			This.bufferSampleRate=sampleRate;//首次处理后,固定下来,后续的每次输入都是相同的
+		}
+		
+		//加入队列,纠正input执行顺序,解码、transform均有可能会导致顺序不一致
+		This.inputQueue[inputN]=pcm;
+		
+		if(This._dest){//已start,可以开始处理队列
+			This._inputProcess();
+		}
+	}
+	,_inputErr:function(errMsg, inputN){
+		this.inputQueue[inputN]=1;//出错了,队列里面也要占个位
+		this.set.onInputError(errMsg, inputN);
+	}
+	//处理input队列
+	,_inputProcess:function(){
+		var This=this;
+		if(!This.bufferSampleRate){
+			return;
+		}
+		
+		var queue=This.inputQueue;
+		for(var i=This.inputQueueIdx+1;i<queue.length;i++){
+			var pcm=queue[i];
+			if(pcm==1){
+				This.inputQueueIdx=i;//跳过出错的input
+				continue;
+			}
+			if(!pcm){
+				return;//之前的input还未进入本方法,退出等待
+			}
+			
+			This.inputQueueIdx=i;
+			queue[i]=null;
+			
+			//推入缓冲,最多两个元素 [堆积的,新的]
+			var pcms=This.pcmBuffer;
+			var pcm0=pcms[0],pcm1=pcms[1];
+			if(pcm0.length){
+				if(pcm1.length){
+					var tmp=new Int16Array(pcm0.length+pcm1.length);
+					tmp.set(pcm0);
+					tmp.set(pcm1,pcm0.length);
+					pcms[0]=tmp;
+				}
+			}else{
+				pcms[0]=pcm1;
+			}
+			pcms[1]=pcm;
+			
+			This._Td+=pcm.length;//更新已输入总时长
+			This._updateTime();
+			This._playLive();//有播放数据了
+		}
+		
+		if(!BufferStreamPlayer.BadAudioBuffer){
+			if(!This.audioBuffer){
+				This._createBuffer(true);
+			}else{
+				This._writeBuffer();
+			}
+		}else{
+			This._writeBad();
+		}
+	}
+	
+	
+	
+	
+	
+	
+	/****************正常的播放处理****************/
+	//创建播放buffer
+	,_createBuffer:function(init){
+		var This=this,set=This.set;
+		if(!init && !This.audioBuffer){
+			return;
+		}
+		
+		var ctx=This._ctx;
+		var sampleRate=This.bufferSampleRate;
+		var bufferSize=sampleRate*(set.bufferSecond||60);//建一个可以持续播放60秒的buffer,循环写入数据播放,大点好简单省事
+		var buffer=ctx.createBuffer(1, bufferSize,sampleRate);
+		
+		var source=ctx.createBufferSource();
+		source.channelCount=1;
+		source.buffer=buffer;
+		source.connect(This._dest);
+		if(set.play){//播放出声音
+			source.connect(ctx.destination);
+		}
+		source.onended=function(){
+			source.disconnect();
+			source.stop();
+			
+			This._createBuffer();//重新创建buffer
+		};
+		source.start();//古董 source.noteOn(0) 不支持onended 放弃支持
+		
+		This.bufferSource=source;
+		This.audioBuffer=buffer;
+		This.audioBufferIdx=0;
+		This._createBufferTime=Date.now();
+		
+		This._writeBuffer();
+	}
+	,_writeBuffer:function(){
+		var This=this,set=This.set;
+		var buffer=This.audioBuffer;
+		var sampleRate=This.bufferSampleRate;
+		var oldAudioBufferIdx=This.audioBufferIdx;
+		if(!buffer){
+			return;
+		}
+		
+		//计算已播放的量,可能已播放过头了,卡了没有数据
+		var playSize=Math.floor((Date.now()-This._createBufferTime)/1000*sampleRate);
+		if(This.audioBufferIdx+0.005*sampleRate<playSize){//5ms动态区间
+			This.audioBufferIdx=playSize;//将写入位置修正到当前播放位置
+		}
+		//写进去了,但还未被播放的量
+		var wnSize=Math.max(0, This.audioBufferIdx-playSize);
+		
+		//这次最大能写入多少;限制到800ms,包括写入了还未播放的
+		var maxSize=buffer.length-This.audioBufferIdx;
+		maxSize=Math.min(maxSize, ~~(0.8*sampleRate)-wnSize);
+		if(maxSize<1){//写不下了,退出
+			return;
+		}
+		
+		if(This._subPause()){//暂停了,不消费缓冲数据
+			return;
+		};
+		var pcms=This.pcmBuffer;
+		var pcm0=pcms[0],pcm1=pcms[1];
+		if(pcm0.length+pcm1.length==0){//无可用数据,退出
+			This._playEnd();//无可播放数据回调
+			return;
+		};
+		This._playLive();//有播放数据了
+		
+		var pcmSize=0,speed=1;
+		var realMode=set.realtime;
+		while(realMode){
+			//************实时模式************
+			//尽量同步播放,避免过大延迟,但始终保持延迟150ms播放新数据,这样每次添加进新数据都是接到还未播放到的最后面,减少引入的杂音,减少网络波动的影响
+			var delaySecond=0.15;
+			
+			//计算当前堆积的量
+			var dSize=wnSize+pcm0.length;
+			var dMax=(realMode.maxDelay||300)/1000 *sampleRate;
+			
+			//堆积的在300ms内按正常播放
+			if(dSize<dMax){
+				//至少要延迟播放新数据
+				var d150Size=Math.floor(delaySecond*sampleRate-dSize-pcm1.length);
+				if(oldAudioBufferIdx==0 && d150Size>0){
+					//开头加上少了的延迟
+					This.audioBufferIdx=Math.max(This.audioBufferIdx, d150Size);
+				}
+				
+				realMode=false;//切换成顺序播放
+				break;
+			}
+			//堆积的太多,配置为全丢弃
+			if(realMode.discardAll){
+				if(dSize>dMax*1.333){//超过400ms,取200ms正常播放,300ms中位数
+					pcm0=This._cutPcm0(Math.round(dMax*0.666-wnSize));
+				}
+				realMode=false;//切换成顺序播放
+				break;
+			}
+			
+			//堆积的太多,要加速播放了,最多播放积压最后3秒的量,超过的直接丢弃
+			pcm0=This._cutPcm0(3*sampleRate-wnSize);
+			
+			speed=1.6;//倍速,重采样
+			//计算要截取出来量
+			pcmSize=Math.min(maxSize, Math.floor((pcm0.length+pcm1.length)/speed));
+			break;
+		}
+		if(!realMode){
+			//*******按顺序取数据播放*********
+			//计算要截取出来量
+			pcmSize=Math.min(maxSize, pcm0.length+pcm1.length);
+		}
+		if(!pcmSize){
+			return;
+		}
+		
+		//截取数据并写入到audioBuffer中
+		This.audioBufferIdx=This._subWrite(buffer,pcmSize,This.audioBufferIdx,speed);
+	}
+	
+	
+	/****************兼容播放处理,播放音质略微差点****************/
+	,_writeBad:function(){
+		var This=this,set=This.set;
+		var buffer=This.audioBuffer;
+		var sampleRate=This.bufferSampleRate;
+		var ctx=This._ctx;
+		
+		//正在播放,5ms不能结束就等待播放完,定时器是10ms
+		if(buffer){
+			var ms=buffer.length/sampleRate*1000;
+			if(Date.now()-This._createBufferTime<ms-5){
+				return;
+			}
+		}
+		
+		//这次最大能写入多少;限制到800ms
+		var maxSize=~~(0.8*sampleRate);
+		var st=set.PlayBufferDisable?0:sampleRate/1000*300;//缓冲播放,不然间隔太短接续爆音明显
+		
+		if(This._subPause()){//暂停了,不消费缓冲数据
+			return;
+		};
+		var pcms=This.pcmBuffer;
+		var pcm0=pcms[0],pcm1=pcms[1];
+		var allSize=pcm0.length+pcm1.length;
+		if(allSize==0 || allSize<st){//无可用数据 不够缓冲量,退出
+			This._playEnd();//无可播放数据回调,最后一丁点会始终等缓冲满导致卡住
+			return;
+		};
+		This._playLive();//有播放数据了
+		
+		var pcmSize=0,speed=1;
+		var realMode=set.realtime;
+		while(realMode){
+			//************实时模式************
+			//计算当前堆积的量
+			var dSize=pcm0.length;
+			var dMax=(realMode.maxDelay||300)/1000 *sampleRate;
+			
+			//堆积的在300ms内按正常播放
+			if(dSize<dMax){
+				realMode=false;//切换成顺序播放
+				break;
+			}
+			//堆积的太多,配置为全丢弃
+			if(realMode.discardAll){
+				if(dSize>dMax*1.333){//超过400ms,取200ms正常播放,300ms中位数
+					pcm0=This._cutPcm0(Math.round(dMax*0.666));
+				}
+				realMode=false;//切换成顺序播放
+				break;
+			}
+			
+			//堆积的太多,要加速播放了,最多播放积压最后3秒的量,超过的直接丢弃
+			pcm0=This._cutPcm0(3*sampleRate);
+			
+			speed=1.6;//倍速,重采样
+			//计算要截取出来量
+			pcmSize=Math.min(maxSize, Math.floor((pcm0.length+pcm1.length)/speed));
+			break;
+		}
+		if(!realMode){
+			//*******按顺序取数据播放*********
+			//计算要截取出来量
+			pcmSize=Math.min(maxSize, pcm0.length+pcm1.length);
+		}
+		if(!pcmSize){
+			return;
+		}
+		
+		//新建buffer,一次性完整播放当前的数据
+		buffer=ctx.createBuffer(1,pcmSize,sampleRate);
+		
+		//截取数据并写入到audioBuffer中
+		This._subWrite(buffer,pcmSize,0,speed);
+		
+		//首尾进行1ms的淡入淡出 大幅减弱爆音
+		FadeInOut(buffer.getChannelData(0), sampleRate);
+		
+		var source=ctx.createBufferSource();
+		source.channelCount=1;
+		source.buffer=buffer;
+		source.connect(This._dest);
+		if(set.play){//播放出声音
+			source.connect(ctx.destination);
+		}
+		source.start();//古董 source.noteOn(0) 不支持onended 放弃支持
+		
+		This.bufferSource=source;
+		This.audioBuffer=buffer;
+		This._createBufferTime=Date.now();
+	}
+	
+	
+	
+	
+	
+	,_cutPcm0:function(pcmNs){//保留堆积的数据到指定的时长数量
+		var pcms=this.pcmBuffer,pcm0=pcms[0];
+		if(pcm0.length>pcmNs){//丢弃超过秒数的
+			pcm0=pcm0.subarray(pcm0.length-pcmNs);
+			pcms[0]=pcm0;
+		}
+		return pcm0;
+	}
+	,_subPause:function(){//暂停了,就不要消费掉缓冲数据了,等待resume再来消费
+		var This=this;
+		if(!This.isPause){
+			return 0;
+		};
+		if(This.set.realtime){//实时模式,丢弃所有未消费的数据,resume时从最新input的数据开始播放
+			This.pcmBuffer=[[],[]];
+		};
+		return 1;
+	}
+	,_subWrite:function(buffer, pcmSize, offset, speed){
+		var This=this;
+		var pcms=This.pcmBuffer;
+		var pcm0=pcms[0],pcm1=pcms[1];
+		
+		//截取数据
+		var pcm=new Int16Array(pcmSize);
+		var i=0,n=0;
+		for(var j=0;n<pcmSize && j<pcm0.length;){//简单重采样
+			pcm[n++]=pcm0[i];
+			j+=speed; i=Math.round(j);
+		}
+		if(i>=pcm0.length){//堆积的消耗完了
+			pcm0=new Int16Array(0);
+			
+			for(j=0,i=0;n<pcmSize && j<pcm1.length;){
+				pcm[n++]=pcm1[i];
+				j+=speed; i=Math.round(j);
+			}
+			if(i>=pcm1.length){
+				pcm1=new Int16Array(0);
+			}else{
+				pcm1=pcm1.subarray(i);
+			}
+			pcms[1]=pcm1;
+		}else{
+			pcm0=pcm0.subarray(i);
+		}
+		pcms[0]=pcm0;
+		
+		
+		//写入到audioBuffer中
+		var channel=buffer.getChannelData(0);
+		for(var i=0;i<pcmSize;i++,offset++){
+			channel[offset]=pcm[i]/0x7FFF;
+		}
+		
+		This._Tc+=pcmSize;//更新已播放时长
+		This._updateTime();
+		
+		return offset;
+	}
+	
+};
+
+var NoStartMsg=function(){
+	return $T("TZPq::{1}未调用start方法",0,BufferStreamPlayerTxt);
+};
+
+
+
+/**pcm数据进行首尾1ms淡入淡出处理,播放时可以大幅减弱爆音**/
+var FadeInOut=BufferStreamPlayer.FadeInOut=function(arr,sampleRate){
+	var sd=sampleRate/1000*1;//浮点数,arr是Int16或者Float32
+	for(var i=0;i<sd;i++){
+		arr[i]*=i/sd;
+	}
+	for(var l=arr.length,i=~~(l-sd);i<l;i++){
+		arr[i]*=(l-i)/sd;
+	}
+};
+
+/**解码音频文件成pcm**/
+var DecodeAudio=BufferStreamPlayer.DecodeAudio=function(arrayBuffer,True,False){
+	var ctx=Recorder.GetContext();
+	if(!ctx){//强制激活Recorder.Ctx 不支持大概率也不支持解码
+		False&&False($T("iCFC::浏览器不支持音频解码"));
+		return;
+	};
+	if(!arrayBuffer || !(arrayBuffer instanceof ArrayBuffer)){
+		False&&False($T("wE2k::音频解码数据必须是ArrayBuffer"));
+		return;//非ArrayBuffer 有日志但不抛异常 不会走回调
+	};
+	
+	ctx.decodeAudioData(arrayBuffer,function(raw){
+		var src=raw.getChannelData(0);
+		var sampleRate=raw.sampleRate;
+		
+		var pcm=new Int16Array(src.length);
+		for(var i=0;i<src.length;i++){//floatTo16BitPCM 
+			var s=Math.max(-1,Math.min(1,src[i]));
+			s=s<0?s*0x8000:s*0x7FFF;
+			pcm[i]=s;
+		};
+		
+		True&&True({
+			sampleRate:sampleRate
+			,duration:Math.round(src.length/sampleRate*1000)
+			,data:pcm
+		});
+	},function(e){
+		False&&False($T("mOaT::音频解码失败:{1}",0,e&&e.message||"-"));
+	});
+};
+
+var CLog=function(){
+	var v=arguments; v[0]="["+BufferStreamPlayerTxt+"]"+v[0];
+	Recorder.CLog.apply(null,v);
+};
+Recorder[BufferStreamPlayerTxt]=BufferStreamPlayer;
+
+	
+}));

+ 372 - 0
src/extensions/create-audio.nmn2pcm.js

@@ -0,0 +1,372 @@
+/***
+简单用 正弦波、方波、锯齿波、三角波 函数生成一段音乐简谱的pcm数据,主要用于测试时提供音频数据。本可音频生成插件可以移植到其他语言环境,如需定制可联系作者
+https://github.com/xiangyuecn/Recorder
+
+此插件在线生成测试:assets/runtime-codes/test.create-audio.nmn2pcm.js
+
+var pcmData=Recorder.NMN2PCM(set);
+	set配置:{
+		texts:""|["",""] 简谱格式化文本,如果格式不符合要求,将会抛异常
+		sampleRate: 生成pcm的采样率,默认48000;取值不能过低,否则会削除高音
+		timbre: 音色,默认2.0(使用音符对应频率的一个倍频),取值>=1.0
+		meterDuration: 一拍时长,毫秒,默认600ms
+		muteDuration: 音符之间的静默,毫秒,0时无静默,默认meterDur/4(最大50ms)
+		beginDuration: 开头的静默时长,毫秒,0时无静默,默认为200ms
+		endDuration: 结尾的静默时长,毫秒,0时无静默,默认为200ms
+		
+		volume: 音量,默认0.3,取值范围0.0-1.0(最大值1)
+		waveType: 波形发生器类型,默认"sine",取值:sine(正弦波)、square(方波,volume应当减半)、sawtooth(锯齿波)、triangle(三角波)
+	}
+
+	texts格式:单个文本,或文本数组
+		- 四分音符(一拍):低音: 1.-7. 中音: 1-7 高音: 1'-7' 休止符(静音):0
+		- 音符后面用 "." 表示低音(尽量改用".":".." 倍低音,"..." 超低音)
+		- 音符后面用 "'" 表示高音(尽量改用"'":"''" 倍高音,"'''" 超高音)
+		- 音符之间用 "|" 或 " " 分隔一拍
+		- 一拍里面多个音符用 "," 分隔,每个音按权重分配这一拍的时长占比,如:“6,7”为一拍,6、7各占1/2拍,相当于八分音符
+		
+		- 音符后面用 "-" 表示二分音符,简单计算为1+1=2拍时长,几个-就加几拍
+		- 音符后面用 "_" 表示八分音符;两两在一拍里面的音符可以免写_,自动会按1/2分配;一拍里面只有一个音时这拍会被简单计算为1/2=0.5拍;其他情况计算会按权重分配这一拍的时长(复杂),如:“6,7_”为1/2+1/2/2=0.75拍(“6*,7_”才是(1+0.5)/2+1/2/2=1拍),其中6权重1分配1/2=0.5拍,7权重0.5分配1/2/2=0.25拍;多加一个"_"就多除个2:“6_,7_”是1/2+1/2=1拍(等同于“6,7”可免写_);“6__,7__”是1/2/2+1/2/2=0.5拍;只要权重加起来是整数就算作完整的1拍
+		- 音符后面用 "*" 表示1+0.5=1.5拍,多出来的1/2计算和_相同(复杂),"**"两个表示加0.25
+		
+		- 可以使用 "S"(sine) "Q"(square) "A"(sawtooth) "T"(triangle) 来切换后续波形发生器类型(按一拍来书写,但不占用时长),类型后面可以接 "(2.0)" 来设置音色,接 "[0.5]" 来设置音量(为set.volume*0.5);特殊值 "R"(reset) 可重置类型成set配置值,如果R后面没有接音色或音量也会被重置;比如:"1 2|A(4.0)[0.6] 3 4 R|5 6",其中12 56使用set配置的类型和音色音量,34使用锯齿波、音色4.0、音量0.18=0.3*0.6
+		
+		- 如果同时有多个音,必须提供数组格式,每个音单独提供一个完整简谱(必须同步对齐)
+
+	返回结果:{
+		pcm: Int16Array,pcm数据
+		duration: 123 pcm的时长,单位毫秒
+		set: {...} 使用的set配置
+		warns: [] 不适合抛异常的提示消息
+	}
+
+Recorder.NMN2PCM.GetExamples() 可获取内置的简谱
+***/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var NMN2PCM=function(set){
+	var texts=set.texts||[]; if(typeof(texts)=="string") texts=[texts];
+	var setSR=set.sampleRate, sampleRate=setSR; if(!sampleRate || sampleRate<1)sampleRate=48000;
+	var meterDur=set.meterDuration||600;
+	var timbre=set.timbre||2; if(timbre<1)timbre=1;
+	
+	var volume=set.volume; if(volume==null)volume=0.3;
+	volume=Math.max(0,volume); volume=Math.min(1,volume);
+	
+	var waveType=set.waveType||"";
+	if(",sine,square,sawtooth,triangle,".indexOf(","+waveType+",")==-1)waveType="";
+	waveType=waveType||"sine";
+	
+	var muteDur=set.muteDuration;
+	if(muteDur==null || muteDur<0){
+		muteDur=meterDur/4; if(muteDur>50)muteDur=50;
+	}
+	var mute0=new Int16Array(sampleRate*muteDur/1000);
+	
+	var beginDur=set.beginDuration;
+	if(beginDur==null || beginDur<0) beginDur=200;
+	var beginMute=new Int16Array(sampleRate*beginDur/1000);
+	var endDur=set.endDuration;
+	if(endDur==null || endDur<0) endDur=200;
+	var endMute=new Int16Array(sampleRate*endDur/1000);
+	
+	//生成C调频率 A=440 国际标准音
+	var s=function(s){ return 440/Math.pow(2,s/12) };
+	var Freqs=[s(9),s(7),s(5),s(4),s(2),s(0),s(-2)];
+	var FreqMP={};
+	for(var i=1;i<=7;i++){
+		var v=Freqs[i-1];
+		FreqMP[i+"..."]=v/8;
+		FreqMP[i+".."]=v/4;
+		FreqMP[i+"."]=v/2;
+		FreqMP[i]=v;
+		FreqMP[i+"'"]=v*2;
+		FreqMP[i+"''"]=v*4;
+		FreqMP[i+"'''"]=v*8;
+	}
+	
+	var tracks=[],freqMax=0,freqMin=90000;
+	for(var iT=0;setSR!=-1 && iT<texts.length;iT++){
+		var meters=texts[iT].split(/[\s\|]+/);
+		var buffers=[],size=0,wType=waveType,wTimbre=timbre,wVol=volume;
+		for(var i0=0;i0<meters.length;i0++){
+			var txt0=meters[i0]; if(!txt0)continue;
+			var v0=txt0.charCodeAt(0);
+			if(v0<48 || v0>55){//不是0-7,切换波形或音色
+				var m=/^(\w)(?:\((.+)\)|\[(.+)\])*$/.exec(txt0)||[],mT=m[1];
+				var m=/\((.+)\)/.exec(txt0)||[],mTb=m[1];
+				var m=/\[(.+)\]/.exec(txt0)||[],mVol=m[1];
+				if(mT=="R"){ wType=waveType;wTimbre=timbre;wVol=volume; }
+				else if(mT=="S") wType="sine";
+				else if(mT=="Q") wType="square";
+				else if(mT=="A") wType="sawtooth";
+				else if(mT=="T") wType="triangle";
+				else mT="";
+				if(!mT||mTb&&!+mTb||mVol&&!+mVol)throw new Error("Invalid: "+txt0);
+				if(mTb)wTimbre=+mTb;
+				if(mVol)wVol=volume*mVol;
+				continue;
+			}
+			var ys=txt0.split(",");//一拍里面的音符
+			var durTotal=meterDur; //一拍的时长,如果里面有+,代表多拍
+			var bTotal=0,hasG=0,hasX=0;
+			for(var i2=0;i2<ys.length;i2++){//先计算出每个音符的占用时长比例
+				var vs=ys[i2].split("");
+				var o={ y:vs[0],b:1,t:wType,tb:wTimbre,vol:wVol }; ys[i2]=o;
+				for(var i3=1;i3<vs.length;i3++){
+					var v=vs[i3];
+					if(v=="'") o.y+="'";
+					else if(v==".") o.y+=".";
+					else if(v=="-"){ o.b+=1; durTotal+=meterDur; }
+					else if(v=="_"){ o.b/=2; hasG=1; }
+					else if(v=="*" && !hasX){ o.b+=0.5; hasX=0.5;
+							if(vs[i3+1]=="*"){ o.b-=0.25; hasX=0.25; i3++; } }
+					else throw new Error($T("3RBa::符号[{1}]无效:{2}",0,v,txt0));
+				}
+				bTotal+=o.b;
+			}
+			if(bTotal%1>0){
+				if(hasG){//"_"不够数量,减掉时间
+					durTotal*=bTotal/Math.ceil(bTotal);
+				}else if(hasX){//"*"加上1/2|1/4拍的时间
+					durTotal+=meterDur*hasX;
+				}
+			}
+			durTotal-=ys.length*muteDur;//减掉中间的静默
+			for(var i2=0;i2<ys.length;i2++){//生成每个音符的pcm
+				var o=ys[i2],wType=o.t,wTimbre=o.tb,wVol=o.vol,freq=FreqMP[o.y]||0;
+				if(!freq && o.y!="0") throw new Error($T("U212::音符[{1}]无效:{2}",0,o.y,txt0));
+				freq=freq*wTimbre;
+				var dur=durTotal*o.b/bTotal;
+				var pcm=new Int16Array(Math.round(dur/1000*sampleRate));
+				if(freq){
+					freqMax=Math.max(freqMax,freq);
+					freqMin=Math.min(freqMin,freq);
+					//不同波形算法取自 https://github.com/cristovao-trevisan/wave-generator/blob/master/index.js
+					if(wType=="sine"){//正弦波
+						var V=(2 * Math.PI) * freq / sampleRate;
+						for(var i=0;i<pcm.length;i++){
+							var v=wVol*Math.sin(V * i);
+							pcm[i]=Math.max(-1,Math.min(1,v))*0x7FFF;
+						}
+					}else if(wType=="square"){//方波
+						var V=sampleRate / freq;
+						for(var i=0;i<pcm.length;i++){
+							var v=wVol*((i % V) < (V / 2) ? 1 : -1);
+							pcm[i]=Math.max(-1,Math.min(1,v))*0x7FFF;
+						}
+					}else if(wType=="sawtooth"){//锯齿波
+						var V=sampleRate / freq;
+						for(var i=0;i<pcm.length;i++){
+							var v=wVol*(-1 + 2 * (i % V) / V);
+							pcm[i]=Math.max(-1,Math.min(1,v))*0x7FFF;
+						}
+					}else if(wType=="triangle"){//三角波
+						var V=sampleRate / freq;
+						for(var i=0;i<pcm.length;i++){
+							var Vi = (i + V / 4) % V;
+							var v=wVol*(Vi<V/2?(-1+4*Vi/V):(3-4*Vi/V));
+							pcm[i]=Math.max(-1,Math.min(1,v))*0x7FFF;
+						}
+					}
+					var pcmDur4=~~(pcm.length/sampleRate*1000/4)||1;
+					FadeInOut(pcm,sampleRate,Math.min(pcmDur4, 10));
+				}
+				
+				var mute=mute0; if(!buffers.length)mute=beginMute;
+				buffers.push(mute); size+=mute.length;
+				
+				buffers.push(pcm); size+=pcm.length;
+			}
+		}
+		if(size>0){
+			buffers.push(endMute); size+=endMute.length;
+			tracks.push({buffers:buffers,size:size});
+		}
+	}
+	tracks.sort(function(a,b){return b.size-a.size});
+	
+	var pcm=new Int16Array(tracks[0]&&tracks[0].size||0);
+	for(var iT=0;iT<tracks.length;iT++){
+		var o=tracks[iT],buffers=o.buffers,size=o.size;
+		if(iT==0){
+			for(var i=0,offset=0;i<buffers.length;i++){
+				var buf=buffers[i];
+				pcm.set(buf,offset);
+				offset+=buf.length;
+			}
+		}else{
+			var diffMs=(pcm.length-size)/sampleRate*1000;
+			if(diffMs>10){//10毫秒误差
+				throw new Error($T("7qAD::多个音时必须对齐,相差{1}ms",0,diffMs));
+			};
+			for(var i=0,offset=0;i<buffers.length;i++){
+				var buf=buffers[i];
+				for(var j=0;j<buf.length;j++){
+					var data_mix,data1=pcm[offset],data2=buf[j];
+					
+					//简单混音算法 https://blog.csdn.net/dancing_night/article/details/53080819
+					if(data1<0 && data2<0){
+						data_mix = data1+data2 - (data1 * data2 / -0x7FFF);  
+					}else{
+						data_mix = data1+data2 - (data1 * data2 / 0x7FFF);
+					};
+					
+					pcm[offset++]=data_mix;
+				}
+			}
+		}
+	}
+	
+	var dur=Math.round(pcm.length/sampleRate*1000);
+	var Warns=[],minSR=~~(freqMax*2);
+	if(freqMax && sampleRate<minSR){
+		var msg="sampleRate["+sampleRate+"] should be greater than "+minSR;
+		Warns.push(msg); Recorder.CLog("NMN2PCM: "+msg,3);
+	}
+	
+	return {pcm:pcm, duration:dur, warns:Warns, set:{
+		texts:texts, sampleRate:sampleRate, timbre:timbre, meterDuration:meterDur
+		,muteDuration:muteDur, beginDuration:beginDur, endDuration:endDur
+		,volume:volume,waveType:waveType
+	}};
+};
+
+
+/**pcm数据进行首尾1ms淡入淡出处理,播放时可以大幅减弱爆音**/
+var FadeInOut=NMN2PCM.FadeInOut=function(arr,sampleRate,dur){
+	var sd=sampleRate/1000*(dur||1);//浮点数,arr是Int16或者Float32
+	for(var i=0;i<sd;i++){
+		arr[i]*=i/sd;
+	}
+	for(var l=arr.length,i=~~(l-sd);i<l;i++){
+		arr[i]*=(l-i)/sd;
+	}
+};
+
+
+
+
+
+
+/***内置部分简谱*****/
+NMN2PCM.GetExamples=function(){ return {
+
+DFH:{//前3句,https://www.hnchemeng.com/liux/201807/68393.html
+	name:"东方红"
+	,get:function(sampleRate){
+		return NMN2PCM({ //https://www.bilibili.com/video/BV1VW4y1v7nY?p=2
+			sampleRate:sampleRate
+			,meterDuration:1000
+			,timbre:3
+			,texts:"5 5,6|2-|1 1,6.|2-|5 5|6,1' 6,5|1 1,6.|2-"
+		});
+	}
+}
+,HappyBirthday:{//4句,https://www.zaoxu.com/jjsh/bkdq/310228.html
+	name:$T("QGsW::祝你生日快乐")
+	,get:function(sampleRate){
+		return NMN2PCM({
+			sampleRate:sampleRate
+			,meterDuration:450
+			,timbre:4
+			,waveType:"triangle", volume:0.15
+			,texts:"5.,5. 6. 5.|1 7.-|5.,5. 6. 5.|2 1-|5.,5. 5 3|1 7. 6.|4*,4_ 3 1|2 1-"
+		});
+	}
+}
+,LHC:{//节选一段,https://www.qinyipu.com/jianpu/jianpudaquan/41703.html
+	name:"兰花草(洒水版)"
+	,get:function(sampleRate){
+		return NMN2PCM({
+			sampleRate:sampleRate
+			,meterDuration:650
+			,timbre:4
+			,texts:"6.,3 3,3|3* 2_|1*,2_ 1,7.|6.-|6,6 6,6|6* 5_|3_,5_,5 5,4|3-|3,3_,6_ 6,5|3* 2_|1*,2_ 1,7.|6. 3.|3.,1 1,7.|6.* 2__,3__|2*,1_ 7._,7._,5.|6.-"
+		});
+	}
+}
+,ForElise:{//节选一段,https://www.qinyipu.com/jianpu/chunyinle/3023.html
+	name:$T("emJR::致爱丽丝")
+	,get:function(sampleRate){
+		return NMN2PCM({
+			sampleRate:sampleRate
+			,meterDuration:550
+			,muteDuration:20
+			,timbre:6
+			,texts:"3',2'|3',2' 3',7 2',1'|"
+				+"6 0,1 3,6|7 0,3 5,7|1' 0 3',2'|"
+				+"3',2' 3',7 2',1'|6 0,1 3,6|7 0,3 1',7|"
+				+"6 0,7 1',2'|3' 0,5 4',3'|2' 0,4 3',2'|1' 0,3 2',1'|"
+				+"7"
+		});
+	}
+}
+,Canon_Right:{//节选一段,https://www.cangqiang.com.cn/d/32153.html
+	name:$T("GsYy::卡农-右手简谱")
+	,get:function(sampleRate){
+		return NMN2PCM({
+			sampleRate:sampleRate
+			,meterDuration:700
+			,texts:"1',7 1',3 5 6,7|"
+				+"1' 3' 5',3' 5',6'|4',3' 2',4' 3',2' 1',7|      7 1',2'|"
+				+"5',3'_,4'_ 5',3'_,4'_ 5',5,6,7 1',2',3',4'|3',1'_,2'_ 3',3_,4_ 5,6,5,4 5,1',7,1'|"
+				+"6,1'_,7_ 6,5_,4_ 5,4,3,4 5,6,7,1'|6,1'_,7_ 1',7_,6_ 7,6,7,1' 2'_,1'_,7|1'-"
+		});
+	}
+}
+,Canon:{//开头一段,https://www.kanpula.com/jianpu/21316.html
+	name:$T("bSFZ::卡农")
+	,get:function(sampleRate){
+		var txt1="",txt2="",txt3="",txt4="";
+		//(1)
+		txt1+="3'---|2'---|1'---|7---|";
+		txt2+="1'---|7---|6---|5---|";
+		txt3+="5---|5---|3---|3---|";
+		txt4+="R[0.3] 1. 5. 1 3|5.. 2. 5. 7.|6.. 3. 6. 1|3.. 7.. 3. 5.|";
+		//(5)
+		txt1+="6---|5---|6---|7---|";
+		txt2+="4---|3---|4---|5---|";
+		txt3+="1---|1---|1---|2---|";
+		txt4+="4.. 1. 4. 6.|1. 5. 1 3|4.. 1. 4. 6.|5.. 2. 5. 7.|";
+		//(9)
+		txt1+="3'---|2'---|1'---|7---|";
+		txt2+="1'---|7---|6---|5---|";
+		txt3+="5---|5---|3---|3-- 5'|";
+		txt4+="1. 5. 1 3|5.. 2. 5. 7.|6.. 3. 6. 1|3.. 7.. 3. 5.|";
+		//(13)
+		txt1+="4' 3' 2' 4'|3' 2' 1' 5|6- 6 1'|7 1' 2'-|";
+		txt2+="4.. 1. 4. 6.|1. 5. 1 3|4.. 1. 4. 6.|5.. 2. 5. 7.|";
+		txt3+="0---|0---|0---|0---|";
+		txt4+="0---|0---|0---|0---|";
+		//(17)
+		txt1+="3',5 1'_ 5' 5_ 3'|3' 4' 3' 2'|1',3 6_ 3' 3_ 1'|1' 2' 1' 7|";
+		txt2+="1. 5. 1 3|5.. 2. 5. 7.|6.. 3. 6. 1|3.. 7.. 3. 5.|";
+		txt3+="0---|0---|0---|0---|";
+		txt4+="0---|0---|0---|0---|";
+		//(21)
+		txt1+="6,1 4_ 1' 1_ 6|5,1 3_ 1' 1_ 5|6,1 4_ 1' 1_ 6|7 7 1' 2'|";
+		txt2+="4.. 1. 4. 6.|1. 5. 1 3|4.. 1. 4. 6.|5..,5. 5..,5. 6..,6. 6..,6.|";
+		txt3+="0---|0---|0---|0---|";
+		txt4+="0---|0---|0---|0---|";
+		
+		return NMN2PCM({
+			sampleRate:sampleRate
+			,meterDuration:500
+			,texts:[txt1,txt2,txt3,txt4]
+		});
+	}
+}
+}
+};
+
+
+Recorder.NMN2PCM=NMN2PCM;
+
+}));

+ 268 - 0
src/extensions/dtmf.decode.js

@@ -0,0 +1,268 @@
+/*
+录音 Recorder扩展,DTMF(电话拨号按键信号)解码器,解码得到按键值
+使用本扩展需要引入lib.fft.js支持
+
+本扩展识别DTMF按键准确度高,误识别率低,支持识别120ms以上按键间隔+30ms以上的按键音,纯js实现易于移植
+
+使用场景:电话录音软解,软电话实时提取DTMF按键信号等
+https://github.com/xiangyuecn/Recorder
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+/*
+参数:
+	pcmData:[Int16,...] pcm一维数组,原则上一次处理的数据量不要超过10秒,太长的数据应当分段延时处理
+	sampleRate: 123 pcm的采样率
+	prevChunk: null || {} 上次的返回值,用于连续识别
+	
+返回:
+	chunk:{
+		keys:[keyItem,...] 识别到的按键,如果未识别到数组长度为0
+				keyItem:{
+					key:"" //按键值 0-9 #*
+					time:123 //所在的时间位置,ms
+				}
+		
+		//以下用于下次接续识别
+		lastIs:"" "":mute {}:match 结尾处是什么
+		lastCheckCount:0 结尾如果是key,此时的检查次数
+		prevIs:"" "":null {}:match 上次疑似检测到了什么
+		totalLen:0 总采样数,相对4khz
+		pcm:[Int16,...] 4khz pcm数据
+		checkFactor:3 信号检查因子,取值1,2,3,默认为3不支持低于32ms的按键音检测,当需要检测时可以设为2,当信号更恶劣时设为1,这样将会减少检查的次数,导致错误识别率变高
+		debug:false 是否开启调试日志
+	}
+*/
+Recorder.DTMF_Decode=function(pcmData,sampleRate,prevChunk){
+	prevChunk||(prevChunk={});
+	var lastIs=prevChunk.lastIs||"";
+	var lastCheckCount=prevChunk.lastCheckCount==null?99:prevChunk.lastCheckCount;
+	var prevIs=prevChunk.prevIs||"";
+	var totalLen=prevChunk.totalLen||0;
+	var prevPcm=prevChunk.pcm;
+	var checkFactor=prevChunk.checkFactor||0;
+	var debug=prevChunk.debug;
+	
+	var keys=[];
+	
+	if(!Recorder.LibFFT){
+		throw new Error($T.G("NeedImport-2",["DTMF_Decode","src/extensions/lib.fft.js"]));
+	};
+	var bufferSize=256;//小一点每次处理的时长不会太长,也不要太小影响分辨率
+	var fft=Recorder.LibFFT(bufferSize);
+	
+	
+	/****初始值计算****/
+	var windowSize=bufferSize/4;//滑动窗口大小,取值为4的原因:64/4=16ms,16ms*(3-1)=32ms,保证3次取值判断有效性
+	var checkCount=checkFactor||3;//只有3次连续窗口内计算结果相同判定为有效信号或间隔
+	var muteCount=3;//两个信号间的最小间隔,3个窗口大小
+	var startTotal=totalLen;
+	
+	/****将采样率降低到4khz,单次fft处理1000/(4000/256)=64ms,分辨率4000/256=15.625hz,允许连续dtmf信号间隔128ms****/
+	var stepFloat=sampleRate/4000;
+	
+	var newSize=Math.floor(pcmData.length/stepFloat);
+	totalLen+=newSize;
+	var pos=0;
+	if(prevPcm&&prevPcm.length>bufferSize){//接上上次的数据,继续滑动
+		pos=windowSize*(checkCount+1);
+		newSize+=pos;
+		startTotal-=pos;
+	};
+	var arr=new Int16Array(newSize);
+	if(pos){
+		arr.set(prevPcm.subarray(prevPcm.length-pos));//接上上次的数据,继续滑动
+	};
+	
+	for(var idxFloat=0;idxFloat<pcmData.length;pos++,idxFloat+=stepFloat){
+		//简单抽样
+		arr[pos]=pcmData[Math.round(idxFloat)];
+	};
+	pcmData=arr;
+	sampleRate=4000;
+	
+	
+	var freqStep=sampleRate/bufferSize;//分辨率
+	var logMin=20;//粗略计算信号强度最小值,此值是先给0再根据下面的Math.log(fv)多次【测试】(下面一个log)出来的
+	
+	
+	/****循环处理所有数据,识别出所有按键信号****/
+	for(var i0=0; i0+bufferSize<=pcmData.length; i0+=windowSize){
+		var arr=pcmData.subarray(i0,i0+bufferSize);
+		var freqs=fft.transform(arr);
+		var freqPs=[];
+		
+		var fv0=0,p0=0,v0=0,vi0=0, fv1=0,p1=0,v1=0,vi1=0;//查找高群和低群
+		for(var i2=0;i2<freqs.length;i2++){
+			var fv=freqs[i2];
+			var p=Math.log(fv);//粗略计算信号强度
+			freqPs.push(p);
+			var v=(i2+1)*freqStep;
+			if(p>logMin){
+				if(fv>fv0 && v<1050){
+					fv0=fv;
+					p0=p;
+					v0=v;
+					vi0=i2;
+				}else if(fv>fv1 && v>1050){
+					fv1=fv;
+					p1=p;
+					v1=v;
+					vi1=i2;
+				};
+			};
+		};
+		var pv0 =-1, pv1=-1;
+		if(v0>600 && v1<1700 && Math.abs(p0-p1)<2.5){//高低频的幅度相差不能太大,此值是先给个大值再多次【测试】(下面一个log)得出来的
+			//波形匹配度:两个峰值之间应当是深V型曲线,如果出现大幅杂波,可以直接排除掉
+			var isV=1;
+			//先找出谷底
+			var pMin=p0,minI=0;
+			for(var i2=vi0;i2<vi1;i2++){
+				var v=freqPs[i2];
+				if(v && v<pMin){//0不作数
+					pMin=v;
+					minI=i2;
+				};
+			};
+			var xMax=(p0-pMin)*0.5//允许幅度变化最大值
+			//V左侧,下降段
+			var curMin=p0;
+			for(var i2=vi0;isV&&i2<minI;i2++){
+				var v=freqPs[i2];
+				if(v<=curMin){
+					curMin=v;
+				}else if(v-curMin>xMax){
+					isV=0;//下降段检测到过度上升
+				};
+			};
+			//V右侧,上升段
+			var curMax=pMin;
+			for(var i2=minI;isV&&i2<vi1;i2++){
+				var v=freqPs[i2];
+				if(v>=curMax){
+					curMax=v;
+				}else if(curMax-v>xMax){
+					isV=0;//上升段检测到过度下降
+				};
+			};
+			
+			if(isV){
+				pv0=FindIndex(v0, DTMF_Freqs[0], freqStep);
+				pv1=FindIndex(v1, DTMF_Freqs[1], freqStep);
+			};
+		};
+		
+		
+		var key="";
+		if (pv0 >= 0 && pv1 >= 0) {
+			key = DTMF_Chars[pv0][pv1];
+			if(debug)console.log(key,Math.round((startTotal+i0)/sampleRate*1000),p0.toFixed(2),p1.toFixed(2),Math.abs(p0-p1).toFixed(2)); //【测试】得出数值
+			
+			if(lastIs){
+				if(lastIs.key==key){//有效,增加校验次数
+					lastCheckCount++;
+				}else{//异常数据,恢复间隔计数
+					key="";
+					lastCheckCount=lastIs.old+lastCheckCount;
+				};
+			}else{
+				//没有连续的信号,检查是否在100ms内有检测到信号,当中间是断开的那种
+				if(prevIs && prevIs.old2 && prevIs.key==key){
+					if(startTotal+i0-prevIs.start<100*sampleRate/1000){
+						lastIs=prevIs;
+						lastCheckCount=prevIs.old2+1;
+						if(debug)console.warn("接续了开叉的信号"+lastCheckCount);
+					};
+				};
+				if(!lastIs){
+					if(lastCheckCount>=muteCount){//间隔够了,开始按键识别计数
+						lastIs={key:key,old:lastCheckCount,old2:lastCheckCount,start:startTotal+i0,pcms:[],use:0};
+						lastCheckCount=1;
+					}else{//上次识别以来间隔不够,重置间隔计数
+						key="";
+						lastCheckCount=0;
+					};
+				};
+			};
+		}else{
+			if(lastIs){//下一个,恢复间隔计数
+				lastIs.old2=lastCheckCount;
+				lastCheckCount=lastIs.old+lastCheckCount;
+			};
+		};
+		
+		if(key){
+			if(debug)lastIs.pcms.push(arr);
+			//按键有效,并且未push过
+			if(lastCheckCount>=checkCount && !lastIs.use){
+				lastIs.use=1;
+				keys.push({
+					key:key
+					,time:Math.round(lastIs.start/sampleRate*1000)
+				});
+			};
+			//重置间隔数据
+			if(lastIs.use){
+				if(debug)console.log(key+"有效按键",lastIs);
+				lastIs.old=0;
+				lastIs.old2=0;
+				lastCheckCount=0;
+			};
+		}else{
+			//未发现按键
+			if(lastIs){
+				if(debug)console.log(lastIs) //测试,输出疑似key
+				prevIs=lastIs;
+			};
+			lastIs="";
+			lastCheckCount++;
+		};
+	};
+	
+	return {
+		keys:keys
+		
+		,lastIs:lastIs
+		,lastCheckCount:lastCheckCount
+		,prevIs:prevIs
+		,totalLen:totalLen
+		,pcm:pcmData
+		,checkFactor:checkFactor
+		,debug:debug
+	};
+};
+
+
+
+var DTMF_Freqs = [
+	[697, 770, 852, 941],
+	[1209, 1336, 1477, 1633]
+];
+var DTMF_Chars = [
+	["1", "2", "3", "A"],
+	["4", "5", "6", "B"],
+	["7", "8", "9", "C"],
+	["*", "0", "#", "D"],
+];
+var FindIndex=function(freq, freqs, freqStep){
+	var idx=-1,idxb=1000;
+	for(var i=0;i<freqs.length;i++){
+		var xb=Math.abs(freqs[i]-freq);
+		if(idxb>xb){
+			idxb=xb;
+			if(xb<freqStep*2){//最多2个分辨率内误差
+				idx=i;
+			};
+		};
+	};
+	return idx;
+};
+	
+}));

+ 196 - 0
src/extensions/dtmf.encode.js

@@ -0,0 +1,196 @@
+/*
+录音 Recorder扩展,DTMF(电话拨号按键信号)编码生成器,生成按键对应的音频PCM信号
+
+本扩展分两个功能:
+	DTMF_Encode
+	DTMF_EncodeMix
+
+本扩展生成信号代码、原理简单粗暴,纯js实现易于移植,0依赖
+
+使用场景:DTMF按键信号生成,软电话实时发送DTMF按键信号等
+https://github.com/xiangyuecn/Recorder
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+/**
+本方法用来生成单个按键信号pcm数据,属于底层方法,要混合多个按键信号到别的pcm中请用封装好的DTMF_EncodeMix方法
+
+参数:
+	key: 单个按键0-9#*
+	sampleRate:123 要生成的pcm采样率
+	duration:100 按键音持续时间
+	mute:50 按键音前后静音时长
+返回:
+	pcm:[Int16,...],生成单个按键信号
+**/
+Recorder.DTMF_Encode=function(key,sampleRate,duration,mute){
+	var durSize=Math.floor(sampleRate*(duration||100)/1000);
+	var muteSize=Math.floor(sampleRate*(mute==null?50:mute)/1000);
+	var pcm0=new Int16Array(durSize+muteSize*2);
+	var pcm1=new Int16Array(durSize+muteSize*2);
+	
+	// https://github.com/watilde/node-dtfm/blob/master/encode.js
+	var f0=DTMF_Freqs[key][0];
+	var f1=DTMF_Freqs[key][1];
+	var vol=0.3;
+	for(var i=0;i<durSize;i++){
+		var v0=vol*Math.sin((2 * Math.PI) * f0 * (i / sampleRate));
+		var v1=vol*Math.sin((2 * Math.PI) * f1 * (i / sampleRate));
+		pcm0[i+muteSize]=Math.max(-1,Math.min(1,v0))*0x7FFF;
+		pcm1[i+muteSize]=Math.max(-1,Math.min(1,v1))*0x7FFF;
+	};
+	
+	//简单叠加 低群 和 高群 信号
+	Mix(pcm0,0,pcm1,0);
+	return pcm0;
+};
+
+
+/**返回EncodeMix对象,将输入的按键信号混合到持续输入的pcm流中,当.mix(inputPcms)提供的太短的pcm会无法完整放下一个完整的按键信号,所以需要不停调用.mix(inputPcms)进行混合**/
+Recorder.DTMF_EncodeMix=function(set){
+	return new EncodeMix(set);
+};
+var EncodeMix=function(set){
+	var This=this;
+	This.set={
+		duration:100 //按键信号持续时间 ms,最小值为30ms
+		,mute:25 //按键音前后静音时长 ms,取值为0也是可以的
+		,interval:200 //两次按键信号间隔时长 ms,间隔内包含了duration+mute*2,最小值为120ms
+	};
+	for(var k in set){
+		This.set[k]=set[k];
+	};
+	
+	This.keys="";
+	This.idx=0;
+	This.state={keyIdx:-1,skip:0};
+};
+EncodeMix.prototype={
+	/** 添加一个按键或多个按键 "0" "123#*",后面慢慢通过mix方法混合到pcm中,无返回值 **/
+	add:function(keys){
+		this.keys+=keys;
+	}
+	/** 将已添加的按键信号混合到pcm中,pcms:[[Int16,...],...]二维数组,sampleRate:pcm的采样率,index:pcms第一维开始索引,将从这个pcm开始混合。
+	返回混合状态对象。
+	注意:调用本方法会修改pcms中的内容,因此混合结果就在pcms内。 **/
+	,mix:function(pcms,sampleRate,index){
+		index||(index=0);
+		var This=this,set=This.set;
+		var newEncodes=[];
+		
+		var state=This.state;
+		var pcmPos=0;
+		loop:
+		for(var i0=index;i0<pcms.length;i0++){
+			var pcm=pcms[i0];
+			
+			var key=This.keys.charAt(This.idx);
+			if(!key){//没有需要处理的按键,把间隔消耗掉
+				state.skip=Math.max(0, state.skip-pcm.length);
+			} else while(key){
+				//按键间隔处理
+				if(state.skip){
+					var op=pcm.length-pcmPos;
+					if(op<=state.skip){
+						state.skip-=op;
+						pcmPos=0;
+						continue loop;
+					};
+					pcmPos+=state.skip;
+					state.skip=0;
+				};
+				
+				var keyPcm=state.keyPcm;
+				
+				//这个key已经混合过,看看有没有剩余的信号
+				if(state.keyIdx==This.idx){
+					if(state.cur>=keyPcm.length){
+						state.keyIdx=-1;
+					};
+				};
+				//新的key,生成信号
+				if(state.keyIdx!=This.idx){
+					keyPcm=Recorder.DTMF_Encode(key,sampleRate,set.duration,set.mute);
+					state.keyIdx=This.idx;
+					state.cur=0;
+					state.keyPcm=keyPcm;
+					
+					newEncodes.push({
+						key:key
+						,data:keyPcm
+					});
+				};
+				
+				//将keyPcm混合到当前pcm中,实际是替换逻辑
+				var res=Mix(pcm,pcmPos,keyPcm,state.cur,true);
+				state.cur=res.cur;
+				pcmPos=res.last;
+				
+				//下一个按键
+				if(res.cur>=keyPcm.length){
+					This.idx++;
+					key=This.keys.charAt(This.idx);
+					state.skip=Math.floor(sampleRate*(set.interval-set.duration-set.mute*2)/1000);
+				};
+				
+				//当前pcm的位置已消耗完
+				if(res.last>=pcm.length){
+					pcmPos=0;
+					continue loop;//下一个pcm
+				};
+			};
+		};
+		
+		return {
+			newEncodes:newEncodes //本次混合新生成的按键信号列表 [{key:"*",data:[Int16,...]},...],如果没有产生新信号将为空数组
+			,hasNext:This.idx<This.keys.length //是否还有未混合完的信号
+		};
+	}
+};
+
+
+
+
+//teach.realtime.mix_multiple 抄过来的简单混合算法
+var Mix=function(buffer,pos1,add,pos2,mute){
+	for(var j=pos1,cur=pos2;;j++,cur++){
+		if(j>=buffer.length || cur>=add.length){
+			return {
+				last:j
+				,cur:cur
+			};
+		};
+		
+		if(mute){
+			buffer[j]=0;//置为0即为静音
+		};
+		var data_mix,data1=buffer[j],data2=add[cur];
+		
+		//简单混音算法 https://blog.csdn.net/dancing_night/article/details/53080819
+		if(data1<0 && data2<0){
+			data_mix = data1+data2 - (data1 * data2 / -0x7FFF);  
+		}else{
+			data_mix = data1+data2 - (data1 * data2 / 0x7FFF);
+		};
+		
+		buffer[j]=data_mix;
+	};
+};
+
+
+
+var DTMF_Freqs={
+	 '1': [697, 1209] ,'2': [697, 1336] ,'3': [697, 1477] ,'A': [697, 1633]
+	,'4': [770, 1209] ,'5': [770, 1336] ,'6': [770, 1477] ,'B': [770, 1633]
+	,'7': [852, 1209] ,'8': [852, 1336] ,'9': [852, 1477] ,'C': [852, 1633]
+	,'*': [941, 1209] ,'0': [941, 1336] ,'#': [941, 1477] ,'D': [941, 1633]
+};
+
+	
+}));

+ 377 - 0
src/extensions/frequency.histogram.view.js

@@ -0,0 +1,377 @@
+/*
+录音 Recorder扩展,频率直方图显示
+使用本扩展需要引入src/extensions/lib.fft.js支持,直方图特意优化主要显示0-5khz语音部分(线性),其他高频显示区域较小,不适合用来展示音乐频谱,可通过配置fullFreq来恢复成完整的线性频谱,或自行修改源码修改成倍频程频谱(伯德图、对数频谱);本可视化插件可以移植到其他语言环境,如需定制可联系作者
+
+https://github.com/xiangyuecn/Recorder
+
+本扩展核心算法主要参考了Java开源库jmp123 版本0.3 的代码:
+https://www.iteye.com/topic/851459
+https://sourceforge.net/projects/jmp123/files/
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var FrequencyHistogramView=function(set){
+	return new fn(set);
+};
+var ViewTxt="FrequencyHistogramView";
+var fn=function(set){
+	var This=this;
+	var o={
+		/*
+		elem:"css selector" //自动显示到dom,并以此dom大小为显示大小
+			//或者配置显示大小,手动把frequencyObj.elem显示到别的地方
+		,width:0 //显示宽度
+		,height:0 //显示高度
+		
+H5环境以上配置二选一
+		
+		compatibleCanvas: CanvasObject //提供一个兼容H5的canvas对象,需支持getContext("2d"),支持设置width、height,支持drawImage(canvas,...)
+		,width:0 //canvas显示宽度
+		,height:0 //canvas显示高度
+非H5环境使用以上配置
+		*/
+		
+		scale:2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
+		
+		,fps:20 //绘制帧率,不可过高
+		
+		,lineCount:30 //直方图柱子数量,数量的多少对性能影响不大,密集运算集中在FFT算法中
+		,widthRatio:0.6 //柱子线条宽度占比,为所有柱子占用整个视图宽度的比例,剩下的空白区域均匀插入柱子中间;默认值也基本相当于一根柱子占0.6,一根空白占0.4;设为1不留空白,当视图不足容下所有柱子时也不留空白
+		,spaceWidth:0 //柱子间空白固定基础宽度,柱子宽度自适应,当不为0时widthRatio无效,当视图不足容下所有柱子时将不会留空白,允许为负数,让柱子发生重叠
+		,minHeight:0 //柱子保留基础高度,position不为±1时应该保留点高度
+		,position:-1 //绘制位置,取值-1到1,-1为最底下,0为中间,1为最顶上,小数为百分比
+		,mirrorEnable:false //是否启用镜像,如果启用,视图宽度会分成左右两块,右边这块进行绘制,左边这块进行镜像(以中间这根柱子的中心进行镜像)
+		
+		,stripeEnable:true //是否启用柱子顶上的峰值小横条,position不是-1时应当关闭,否则会很丑
+		,stripeHeight:3 //峰值小横条基础高度
+		,stripeMargin:6 //峰值小横条和柱子保持的基础距离
+		
+		,fallDuration:1000 //柱子从最顶上下降到最底部最长时间ms
+		,stripeFallDuration:3500 //峰值小横条从最顶上下降到底部最长时间ms
+		
+		//柱子颜色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
+		,linear:[0,"rgba(0,187,17,1)",0.5,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"]
+		//峰值小横条渐变颜色配置,取值格式和linear一致,留空为柱子的渐变颜色
+		,stripeLinear:null
+		
+		,shadowBlur:0 //柱子阴影基础大小,设为0不显示阴影,如果柱子数量太多时请勿开启,非常影响性能
+		,shadowColor:"#bbb" //柱子阴影颜色
+		,stripeShadowBlur:-1 //峰值小横条阴影基础大小,设为0不显示阴影,-1为柱子的大小,如果柱子数量太多时请勿开启,非常影响性能
+		,stripeShadowColor:"" //峰值小横条阴影颜色,留空为柱子的阴影颜色
+		
+		,fullFreq:false //是否要绘制所有频率;默认false主要绘制5khz以下的频率,高频部分占比很少,此时不同的采样率对频谱显示几乎没有影响;设为true后不同采样率下显示的频谱是不一样的,因为 最大频率=采样率/2 会有差异
+		//当发生绘制时会回调此方法,参数为当前绘制的频率数据和采样率,可实现多个直方图同时绘制,只消耗一个input输入和计算时间
+		,onDraw:function(frequencyData,sampleRate){}
+	};
+	for(var k in set){
+		o[k]=set[k];
+	};
+	This.set=set=o;
+	
+	var cCanvas="compatibleCanvas";
+	if(set[cCanvas]){
+		var canvas=This.canvas=set[cCanvas];
+	}else{
+		if(!isBrowser)throw new Error($T.G("NonBrowser-1",[ViewTxt]));
+		var elem=set.elem;
+		if(elem){
+			if(typeof(elem)=="string"){
+				elem=document.querySelector(elem);
+			}else if(elem.length){
+				elem=elem[0];
+			};
+		};
+		if(elem){
+			set.width=elem.offsetWidth;
+			set.height=elem.offsetHeight;
+		};
+		
+		var thisElem=This.elem=document.createElement("div");
+		thisElem.style.fontSize=0;
+		thisElem.innerHTML='<canvas style="width:100%;height:100%;"/>';
+		
+		var canvas=This.canvas=thisElem.querySelector("canvas");
+		
+		if(elem){
+			elem.innerHTML="";
+			elem.appendChild(thisElem);
+		};
+	};
+	var scale=set.scale;
+	var width=set.width*scale;
+	var height=set.height*scale;
+	if(!width || !height){
+		throw new Error($T.G("IllegalArgs-1",[ViewTxt+" width=0 height=0"]));
+	};
+	
+	canvas.width=width;
+	canvas.height=height;
+	var ctx=This.ctx=canvas.getContext("2d");
+	
+	if(!Recorder.LibFFT){
+		throw new Error($T.G("NeedImport-2",[ViewTxt,"src/extensions/lib.fft.js"]));
+	};
+	This.fft=Recorder.LibFFT(1024);
+	
+	//柱子所在高度
+	This.lastH=[];
+	//峰值小横条所在高度
+	This.stripesH=[];
+};
+fn.prototype=FrequencyHistogramView.prototype={
+	genLinear:function(ctx,colors,from,to){
+		var rtv=ctx.createLinearGradient(0,from,0,to);
+		for(var i=0;i<colors.length;){
+			rtv.addColorStop(colors[i++],colors[i++]);
+		};
+		return rtv;
+	}
+	,input:function(pcmData,powerLevel,sampleRate){
+		var This=this;
+		This.sampleRate=sampleRate;
+		This.pcmData=pcmData;
+		This.pcmPos=0;
+		
+		This.inputTime=Date.now();
+		This.schedule();
+	}
+	,schedule:function(){
+		var This=this,set=This.set;
+		var interval=Math.floor(1000/set.fps);
+		if(!This.timer){
+			This.timer=setInterval(function(){
+				This.schedule();
+			},interval);
+		};
+		
+		var now=Date.now();
+		var drawTime=This.drawTime||0;
+		if(now-This.inputTime>set.stripeFallDuration*1.3){
+			//超时没有输入,顶部横条已全部落下,干掉定时器
+			clearInterval(This.timer);
+			This.timer=0;
+			
+			This.lastH=[];//重置高度再绘制一次,避免定时不准没到底就停了
+			This.stripesH=[];
+			This.draw(null,This.sampleRate);
+			return;
+		};
+		if(now-drawTime<interval){
+			//没到间隔时间,不绘制
+			return;
+		};
+		This.drawTime=now;
+		
+		//调用FFT计算频率数据
+		var bufferSize=This.fft.bufferSize;
+		var pcm=This.pcmData;
+		var pos=This.pcmPos;
+		var arr=new Int16Array(bufferSize);
+		for(var i=0;i<bufferSize&&pos<pcm.length;i++,pos++){
+			arr[i]=pcm[pos];
+		};
+		This.pcmPos=pos;
+		
+		var frequencyData=This.fft.transform(arr);
+		
+		//推入绘制
+		This.draw(frequencyData,This.sampleRate);
+	}
+	,draw:function(frequencyData,sampleRate){
+		var This=this,set=This.set;
+		var ctx=This.ctx;
+		var scale=set.scale;
+		var width=set.width*scale;
+		var height=set.height*scale;
+		var lineCount=set.lineCount;
+		var bufferSize=This.fft.bufferSize;
+		
+		
+		//计算高度位置
+		var position=set.position;
+		var posAbs=Math.abs(set.position);
+		var originY=position==1?0:height;//y轴原点
+		var heightY=height;//最高的一边高度
+		if(posAbs<1){
+			heightY=heightY/2;
+			originY=heightY;
+			heightY=Math.floor(heightY*(1+posAbs));
+			originY=Math.floor(position>0?originY*(1-posAbs):originY*(1+posAbs));
+		};
+		
+		var lastH=This.lastH;
+		var stripesH=This.stripesH;
+		var speed=Math.ceil(heightY/(set.fallDuration/(1000/set.fps)));
+		var stripeSpeed=Math.ceil(heightY/(set.stripeFallDuration/(1000/set.fps)));
+		var stripeMargin=set.stripeMargin*scale;
+		
+		var Y0=1 << (Math.round(Math.log(bufferSize)/Math.log(2) + 3) << 1);
+		var logY0 = Math.log(Y0)/Math.log(10);
+		var dBmax=20*Math.log(0x7fff)/Math.log(10);
+		
+		var fftSize=bufferSize/2,fftSize5k=fftSize;
+		if(!set.fullFreq){//非绘制所有频率时,计算5khz所在位置,8000采样率及以下最高只有4khz
+			fftSize5k=Math.min(fftSize,Math.floor(fftSize*5000/(sampleRate/2)));
+		}
+		var isFullFreq=fftSize5k==fftSize;
+		var line80=isFullFreq?lineCount:Math.round(lineCount*0.8);//80%的柱子位置
+		var fftSizeStep1=fftSize5k/line80;
+		var fftSizeStep2=isFullFreq?0:(fftSize-fftSize5k)/(lineCount-line80);
+		var fftIdx=0;
+		for(var i=0;i<lineCount;i++){
+			// !fullFreq 时不采用jmp123的非线性划分频段,录音语音并不适用于音乐的频率,应当弱化高频部分
+			//80%关注0-5khz主要人声部分 20%关注剩下的高频,这样不管什么采样率都能做到大部分频率显示一致。
+			var start=Math.ceil(fftIdx);
+			if(i<line80){
+				//5khz以下
+				fftIdx+=fftSizeStep1;
+			}else{
+				//5khz以上
+				fftIdx+=fftSizeStep2;
+			};
+			var end=Math.ceil(fftIdx); if(end==start)end++;
+			end=Math.min(end,fftSize);
+			
+			
+			//参考AudioGUI.java .drawHistogram方法
+			
+			//查找当前频段的最大"幅值"
+			var maxAmp=0;
+			if(frequencyData){
+				for (var j=start; j<end; j++) {
+					maxAmp=Math.max(maxAmp,Math.abs(frequencyData[j]));
+				};
+			};
+			
+			//计算音量
+			var dB= (maxAmp > Y0) ? Math.floor((Math.log(maxAmp)/Math.log(10) - logY0) * 17) : 0;
+			var h=heightY*Math.min(dB/dBmax,1);
+			
+			//使柱子匀速下降
+			lastH[i]=(lastH[i]||0)-speed;
+			if(h<lastH[i]){h=lastH[i];};
+			if(h<0){h=0;};
+			lastH[i]=h;
+			
+			var shi=stripesH[i]||0;
+			if(h&&h+stripeMargin>shi) {
+				stripesH[i]=h+stripeMargin;
+			}else{
+				//使峰值小横条匀速度下落
+				var sh =shi-stripeSpeed;
+				if(sh < 0){sh = 0;};
+				stripesH[i] = sh;
+			};
+		};
+		
+		//开始绘制图形
+		ctx.clearRect(0,0,width,height);
+		
+		var linear1=This.genLinear(ctx,set.linear,originY,originY-heightY);//上半部分的填充
+		var stripeLinear1=set.stripeLinear&&This.genLinear(ctx,set.stripeLinear,originY,originY-heightY)||linear1;//上半部分的峰值小横条填充
+		
+		var linear2=This.genLinear(ctx,set.linear,originY,originY+heightY);//下半部分的填充
+		var stripeLinear2=set.stripeLinear&&This.genLinear(ctx,set.stripeLinear,originY,originY+heightY)||linear2;//上半部分的峰值小横条填充
+		
+		//计算柱子间距
+		var mirrorEnable=set.mirrorEnable;
+		var mirrorCount=mirrorEnable?lineCount*2-1:lineCount;//镜像柱子数量翻一倍-1根
+		
+		var widthRatio=set.widthRatio;
+		var spaceWidth=set.spaceWidth*scale;
+		if(spaceWidth!=0){
+			widthRatio=(width-spaceWidth*(mirrorCount+1))/width;
+		};
+		
+		for(var i=0;i<2;i++){
+			var lineFloat=Math.max(1*scale,(width*widthRatio)/mirrorCount);//柱子宽度至少1个单位
+			var lineWN=Math.floor(lineFloat),lineWF=lineFloat-lineWN;//提取出小数部分
+			var spaceFloat=(width-mirrorCount*lineFloat)/(mirrorCount+1);//均匀间隔,首尾都留空,可能为负数,柱子将发生重叠
+			if(spaceFloat>0 && spaceFloat<1){
+				widthRatio=1; spaceFloat=0; //不够一个像素,丢弃不绘制间隔,重新计算
+			}else break;
+		};
+		
+		//绘制
+		var minHeight=set.minHeight*scale;
+		var XFloat=mirrorEnable?(width-lineWN)/2-spaceFloat:0;//镜像时,中间柱子位于正中心
+		for(var iMirror=0;iMirror<2;iMirror++){
+			if(iMirror){ ctx.save(); ctx.scale(-1,1); }
+			var xMirror=iMirror?width:0; //绘制镜像部分,不用drawImage(canvas)进行镜像绘制,提升兼容性(iOS微信小程序bug https://developers.weixin.qq.com/community/develop/doc/000aaca2148dc8a235a0fb8c66b000)
+			
+			//绘制柱子
+			ctx.shadowBlur=set.shadowBlur*scale;
+			ctx.shadowColor=set.shadowColor;
+			for(var i=0,xFloat=XFloat,wFloat=0,x,y,w,h;i<lineCount;i++){
+				xFloat+=spaceFloat;
+				x=Math.floor(xFloat)-xMirror;
+				w=lineWN; wFloat+=lineWF; if(wFloat>=1){ w++; wFloat--; } //小数凑够1像素
+				h=Math.max(lastH[i],minHeight);
+				
+				//绘制上半部分
+				if(originY!=0){
+					y=originY-h;
+					ctx.fillStyle=linear1;
+					ctx.fillRect(x, y, w, h);
+				};
+				//绘制下半部分
+				if(originY!=height){
+					ctx.fillStyle=linear2;
+					ctx.fillRect(x, originY, w, h);
+				};
+				
+				xFloat+=w;
+			};
+			
+			//绘制柱子顶上峰值小横条
+			if(set.stripeEnable){
+				var stripeShadowBlur=set.stripeShadowBlur;
+				ctx.shadowBlur=(stripeShadowBlur==-1?set.shadowBlur:stripeShadowBlur)*scale;
+				ctx.shadowColor=set.stripeShadowColor||set.shadowColor;
+				var stripeHeight=set.stripeHeight*scale;
+				for(var i=0,xFloat=XFloat,wFloat=0,x,y,w,h;i<lineCount;i++){
+					xFloat+=spaceFloat;
+					x=Math.floor(xFloat)-xMirror;
+					w=lineWN; wFloat+=lineWF; if(wFloat>=1){ w++; wFloat--; } //小数凑够1像素
+					h=stripesH[i];
+					
+					//绘制上半部分
+					if(originY!=0){
+						y=originY-h-stripeHeight;
+						if(y<0){y=0;};
+						ctx.fillStyle=stripeLinear1;
+						ctx.fillRect(x, y, w, stripeHeight);
+					};
+					//绘制下半部分
+					if(originY!=height){
+						y=originY+h;
+						if(y+stripeHeight>height){
+							y=height-stripeHeight;
+						};
+						ctx.fillStyle=stripeLinear2;
+						ctx.fillRect(x, y, w, stripeHeight);
+					};
+					
+					xFloat+=w;
+				};
+			};
+		
+			if(iMirror){ ctx.restore(); }
+			if(!mirrorEnable) break;
+		};
+		
+		if(frequencyData){
+			set.onDraw(frequencyData,sampleRate);
+		};
+	}
+};
+Recorder[ViewTxt]=FrequencyHistogramView;
+
+	
+}));

+ 118 - 0
src/extensions/lib.fft.js

@@ -0,0 +1,118 @@
+/*
+时域转频域,快速傅里叶变换(FFT)
+https://github.com/xiangyuecn/Recorder
+
+var fft=Recorder.LibFFT(bufferSize)
+	bufferSize取值2的n次方
+
+fft.bufferSize 实际采用的bufferSize
+fft.transform(inBuffer)
+	inBuffer:[Int16,...] 数组长度必须是bufferSize
+	返回[Float64(Long),...],长度为bufferSize/2
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+/*
+从FFT.java 移植,Java开源库:jmp123 版本0.3
+https://www.iteye.com/topic/851459
+https://sourceforge.net/projects/jmp123/files/
+*/
+Recorder.LibFFT=function(bufferSize){
+	var FFT_N_LOG,FFT_N,MINY;
+	var real, imag, sintable, costable;
+	var bitReverse;
+
+	var FFT_Fn=function(bufferSize) {//bufferSize只能取值2的n次方
+		FFT_N_LOG=Math.round(Math.log(bufferSize)/Math.log(2));
+		FFT_N = 1 << FFT_N_LOG;
+		MINY = ((FFT_N << 2) * Math.sqrt(2));
+		
+		real = [];
+		imag = [];
+		sintable = [0];
+		costable = [0];
+		bitReverse = [];
+
+		var i, j, k, reve;
+		for (i = 0; i < FFT_N; i++) {
+			k = i;
+			for (j = 0, reve = 0; j != FFT_N_LOG; j++) {
+				reve <<= 1;
+				reve |= (k & 1);
+				k >>>= 1;
+			}
+			bitReverse[i] = reve;
+		}
+
+		var theta, dt = 2 * Math.PI / FFT_N;
+		for (i = (FFT_N >> 1) - 1; i > 0; i--) {
+			theta = i * dt;
+			costable[i] = Math.cos(theta);
+			sintable[i] = Math.sin(theta);
+		}
+	}
+
+	/*
+	用于频谱显示的快速傅里叶变换 
+    inBuffer 输入FFT_N个实数,返回 FFT_N/2个输出值(复数模的平方)。 
+	*/
+	var getModulus=function(inBuffer) {
+		var i, j, k, ir, j0 = 1, idx = FFT_N_LOG - 1;
+		var cosv, sinv, tmpr, tmpi;
+		for (i = 0; i != FFT_N; i++) {
+			real[i] = inBuffer[bitReverse[i]];
+			imag[i] = 0;
+		}
+
+		for (i = FFT_N_LOG; i != 0; i--) {
+			for (j = 0; j != j0; j++) {
+				cosv = costable[j << idx];
+				sinv = sintable[j << idx];
+				for (k = j; k < FFT_N; k += j0 << 1) {
+					ir = k + j0;
+					tmpr = cosv * real[ir] - sinv * imag[ir];
+					tmpi = cosv * imag[ir] + sinv * real[ir];
+					real[ir] = real[k] - tmpr;
+					imag[ir] = imag[k] - tmpi;
+					real[k] += tmpr;
+					imag[k] += tmpi;
+				}
+			}
+			j0 <<= 1;
+			idx--;
+		}
+
+		j = FFT_N >> 1;
+		var outBuffer=new Float64Array(j);
+		/*
+		 * 输出模的平方:
+		 * for(i = 1; i <= j; i++)
+		 * 	inBuffer[i-1] = real[i] * real[i] +  imag[i] * imag[i];
+		 * 
+		 * 如果FFT只用于频谱显示,可以"淘汰"幅值较小的而减少浮点乘法运算. MINY的值
+		 * 和Spectrum.Y0,Spectrum.logY0对应.
+		 */
+		sinv = MINY;
+		cosv = -MINY;
+		for (i = j; i != 0; i--) {
+			tmpr = real[i];
+			tmpi = imag[i];
+			if (tmpr > cosv && tmpr < sinv && tmpi > cosv && tmpi < sinv)
+				outBuffer[i - 1] = 0;
+			else
+				outBuffer[i - 1] = Math.round(tmpr * tmpr + tmpi * tmpi);
+		}
+		return outBuffer;
+	}
+	
+	FFT_Fn(bufferSize);
+	return {transform:getModulus,bufferSize:FFT_N};
+};
+
+}));

+ 1155 - 0
src/extensions/sonic.js

@@ -0,0 +1,1155 @@
+/*
+录音 Recorder扩展,音频变速变调转换,本代码从Sonic.java移植
+https://github.com/xiangyuecn/Recorder
+
+Recorder.Sonic(set)
+Recorder.Sonic.Async(set)
+	有两种构造方法:Sonic是同步方法,Sonic.Async是异步方法,同步方法简单直接但处理量大时会消耗大量时间,主要用于一次性的处理;异步方法由WebWorker在后台进行运算处理,但异步方法不一定能成功开启(低版本浏览器),主要用于实时处理。
+	
+	注意:异步方法调用后必须调用flush方法,否则会产生内存泄露。
+	
+【构造初始化参数】
+	set:{
+		sampleRate:待处理pcm的采样率,就是input输入的buffer的采样率
+	}
+
+【功能配置调用函数】同步异步通用,以下num取值正常为0.1-2.0,超过这个范围也是可以的,但不推荐
+	.setPitch(num) num:0.1-n,变调不变速(会说话的汤姆猫),男女变声,只调整音调,不改变播放速度,默认为1.0不调整
+	.setSpeed(num) num:0.1-n,变速不变调(快放慢放),只调整播放速度,不改变音调,默认为1.0不调整
+	.setRate(num) num:0.1-n,变速变调,越小越缓重,越大越尖锐,会改变播放速度和音调,默认为1.0不调整
+	.setVolume(num) num:0.1-n,调整音量,默认为1.0不调整
+	.setChordPitch(bool) bool:默认false,作用未知,不推荐使用
+	.setQuality(num) num:0或1,默认0时会减小输入采样率来提供处理速度,变调时才会用到,不推荐使用
+
+【同步调用方法】
+	.input(buffer) buffer:[Int16,...] 一维数组,输入pcm数据,返回转换后的部分pcm数据,完整输出需要调用flush;返回值[Int16,...]长度可能为0,代表没有数据被转换;此方法是耗时的方法,一次性处理大量pcm需要切片+setTimeout优化
+	.flush() 将残余的未转换的pcm数据完成转换并返回;返回值[Int16,...]长度可能为0,代表没有数据被转换,flush后不能再调用input
+【异步调用方法】
+	.input(buffer,callback) callback:fn(pcm),和同步方法相同,只是返回值通过callback返回
+	.flush(callback) callback:fn(pcm),和同步方法相同,只是返回值通过callback返回
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+//是否支持web worker
+var HasWebWorker=isBrowser && typeof Worker=="function";
+
+function SonicFunction(SonicFunction_set){//用函数包裹方便Web Worker化
+
+//暴露接口
+var fn=function(set){
+	this.set=set;
+	
+	var sonic=Sonic_Class(this);
+	this.sonic=sonic;
+	sonic.New(set.sampleRate,1);
+};
+fn.prototype=SonicFunction.prototype={
+	input:function(buffer){
+		this.sonic.writeShortToStream(buffer);
+		return this.sonic.readShortFromStream();
+	}
+	,flush:function(){
+		this.sonic.flushStream();
+		return this.sonic.readShortFromStream();
+	}
+};
+
+
+
+//java 兼容环境
+var System={
+	arraycopy:function(src,srcPos,dest,destPos,len){
+		for(var i=0;i<len;i++){
+			dest[destPos+i]=src[srcPos+i];
+		};
+	}
+};
+
+
+/* Sonic library
+   Copyright 2010, 2011
+   Bill Cox
+   This file is part of the Sonic Library.
+
+   This file is licensed under the Apache 2.0 license.
+   
+   https://github.com/waywardgeek/sonic/blob/71c51195de71627d7443d05378c680ba756545e8/Sonic.java
+*/
+//Sonic.java 转写 js
+function Sonic_Class(FnObj) {
+
+    var SONIC_MIN_PITCH = 65;
+    var SONIC_MAX_PITCH = 400;
+    // This is used to down-sample some inputs to improve speed
+    var SONIC_AMDF_FREQ = 4000;
+    // The number of points to use in the sinc FIR filter for resampling.
+    var SINC_FILTER_POINTS = 12;
+    var SINC_TABLE_SIZE = 601;
+
+    // Lookup table for windowed sinc function of SINC_FILTER_POINTS points.
+    var sincTable = [
+        0, 0, 0, 0, 0, 0, 0, -1, -1, -2, -2, -3, -4, -6, -7, -9, -10, -12, -14,
+        -17, -19, -21, -24, -26, -29, -32, -34, -37, -40, -42, -44, -47, -48, -50,
+        -51, -52, -53, -53, -53, -52, -50, -48, -46, -43, -39, -34, -29, -22, -16,
+        -8, 0, 9, 19, 29, 41, 53, 65, 79, 92, 107, 121, 137, 152, 168, 184, 200,
+        215, 231, 247, 262, 276, 291, 304, 317, 328, 339, 348, 357, 363, 369, 372,
+        374, 375, 373, 369, 363, 355, 345, 332, 318, 300, 281, 259, 234, 208, 178,
+        147, 113, 77, 39, 0, -41, -85, -130, -177, -225, -274, -324, -375, -426,
+        -478, -530, -581, -632, -682, -731, -779, -825, -870, -912, -951, -989,
+        -1023, -1053, -1080, -1104, -1123, -1138, -1149, -1154, -1155, -1151,
+        -1141, -1125, -1105, -1078, -1046, -1007, -963, -913, -857, -796, -728,
+        -655, -576, -492, -403, -309, -210, -107, 0, 111, 225, 342, 462, 584, 708,
+        833, 958, 1084, 1209, 1333, 1455, 1575, 1693, 1807, 1916, 2022, 2122, 2216,
+        2304, 2384, 2457, 2522, 2579, 2625, 2663, 2689, 2706, 2711, 2705, 2687,
+        2657, 2614, 2559, 2491, 2411, 2317, 2211, 2092, 1960, 1815, 1658, 1489,
+        1308, 1115, 912, 698, 474, 241, 0, -249, -506, -769, -1037, -1310, -1586,
+        -1864, -2144, -2424, -2703, -2980, -3254, -3523, -3787, -4043, -4291,
+        -4529, -4757, -4972, -5174, -5360, -5531, -5685, -5819, -5935, -6029,
+        -6101, -6150, -6175, -6175, -6149, -6096, -6015, -5905, -5767, -5599,
+        -5401, -5172, -4912, -4621, -4298, -3944, -3558, -3141, -2693, -2214,
+        -1705, -1166, -597, 0, 625, 1277, 1955, 2658, 3386, 4135, 4906, 5697, 6506,
+        7332, 8173, 9027, 9893, 10769, 11654, 12544, 13439, 14335, 15232, 16128,
+        17019, 17904, 18782, 19649, 20504, 21345, 22170, 22977, 23763, 24527,
+        25268, 25982, 26669, 27327, 27953, 28547, 29107, 29632, 30119, 30569,
+        30979, 31349, 31678, 31964, 32208, 32408, 32565, 32677, 32744, 32767,
+        32744, 32677, 32565, 32408, 32208, 31964, 31678, 31349, 30979, 30569,
+        30119, 29632, 29107, 28547, 27953, 27327, 26669, 25982, 25268, 24527,
+        23763, 22977, 22170, 21345, 20504, 19649, 18782, 17904, 17019, 16128,
+        15232, 14335, 13439, 12544, 11654, 10769, 9893, 9027, 8173, 7332, 6506,
+        5697, 4906, 4135, 3386, 2658, 1955, 1277, 625, 0, -597, -1166, -1705,
+        -2214, -2693, -3141, -3558, -3944, -4298, -4621, -4912, -5172, -5401,
+        -5599, -5767, -5905, -6015, -6096, -6149, -6175, -6175, -6150, -6101,
+        -6029, -5935, -5819, -5685, -5531, -5360, -5174, -4972, -4757, -4529,
+        -4291, -4043, -3787, -3523, -3254, -2980, -2703, -2424, -2144, -1864,
+        -1586, -1310, -1037, -769, -506, -249, 0, 241, 474, 698, 912, 1115, 1308,
+        1489, 1658, 1815, 1960, 2092, 2211, 2317, 2411, 2491, 2559, 2614, 2657,
+        2687, 2705, 2711, 2706, 2689, 2663, 2625, 2579, 2522, 2457, 2384, 2304,
+        2216, 2122, 2022, 1916, 1807, 1693, 1575, 1455, 1333, 1209, 1084, 958, 833,
+        708, 584, 462, 342, 225, 111, 0, -107, -210, -309, -403, -492, -576, -655,
+        -728, -796, -857, -913, -963, -1007, -1046, -1078, -1105, -1125, -1141,
+        -1151, -1155, -1154, -1149, -1138, -1123, -1104, -1080, -1053, -1023, -989,
+        -951, -912, -870, -825, -779, -731, -682, -632, -581, -530, -478, -426,
+        -375, -324, -274, -225, -177, -130, -85, -41, 0, 39, 77, 113, 147, 178,
+        208, 234, 259, 281, 300, 318, 332, 345, 355, 363, 369, 373, 375, 374, 372,
+        369, 363, 357, 348, 339, 328, 317, 304, 291, 276, 262, 247, 231, 215, 200,
+        184, 168, 152, 137, 121, 107, 92, 79, 65, 53, 41, 29, 19, 9, 0, -8, -16,
+        -22, -29, -34, -39, -43, -46, -48, -50, -52, -53, -53, -53, -52, -51, -50,
+        -48, -47, -44, -42, -40, -37, -34, -32, -29, -26, -24, -21, -19, -17, -14,
+        -12, -10, -9, -7, -6, -4, -3, -2, -2, -1, -1, 0, 0, 0, 0, 0, 0, 0
+    ];
+
+    var inputBuffer;
+    var outputBuffer;
+    var pitchBuffer;
+    var downSampleBuffer;
+    var speed=0;
+    var volume=0;
+    var pitch=0;
+    var rate=0;
+    var oldRatePosition=0;
+    var newRatePosition=0;
+    var useChordPitch=false;
+    var quality=0;
+    var numChannels=0;
+    var inputBufferSize=0;
+    var pitchBufferSize=0;
+    var outputBufferSize=0;
+    var numInputSamples=0;
+    var numOutputSamples=0;
+    var numPitchSamples=0;
+    var minPeriod=0;
+    var maxPeriod=0;
+    var maxRequired=0;
+    var remainingInputToCopy=0;
+    var sampleRate=0;
+    var prevPeriod=0;
+    var prevMinDiff=0;
+    var minDiff=0;
+    var maxDiff=0;
+
+    // Resize the array.
+    function resize(
+        oldArray,
+        newLength)
+    {
+        newLength *= numChannels;
+        var newArray = new Int16Array(newLength);
+        var length = oldArray.length <= newLength? oldArray.length : newLength;
+
+        System.arraycopy(oldArray, 0, newArray, 0, length);
+        return newArray;
+    }
+
+    // Move samples from one array to another.  May move samples down within an array, but not up.
+    function move(
+        dest,
+        destPos,
+        source,
+        sourcePos,
+        numSamples)
+    {
+        System.arraycopy(source, sourcePos*numChannels, dest, destPos*numChannels, numSamples*numChannels);
+    }
+
+    // Scale the samples by the factor.
+    function scaleSamples(
+        samples,
+        position,
+        numSamples,
+        volume)
+    {
+        var fixedPointVolume = Math.floor(volume*4096.0);
+        var start = position*numChannels;
+        var stop = start + numSamples*numChannels;
+
+        for(var xSample = start; xSample < stop; xSample++) {
+            var value = (samples[xSample]*fixedPointVolume) >> 12;
+            if(value > 32767) {
+                value = 32767;
+            } else if(value < -32767) {
+                value = -32767;
+            }
+            samples[xSample] = value;
+        }
+    }
+
+    // Get the speed of the stream.
+    function getSpeed()
+    {
+        return speed;
+    }
+
+    // Set the speed of the stream.
+    function setSpeed(
+        speed_)
+    {
+        speed = speed_;
+    }
+
+    // Get the pitch of the stream.
+    function getPitch()
+    {
+        return pitch;
+    }
+
+    // Set the pitch of the stream.
+    function setPitch(
+        pitch_)
+    {
+        pitch = pitch_;
+    }
+
+    // Get the rate of the stream.
+    function getRate()
+    {
+        return rate;
+    }
+
+    // Set the playback rate of the stream. This scales pitch and speed at the same time.
+    function setRate(
+        rate_)
+    {
+		if(rate!=rate_){//允许任意设置
+			rate = rate_;
+			oldRatePosition = 0;
+			newRatePosition = 0;
+		}
+    }
+
+    // Get the vocal chord pitch setting.
+    function getChordPitch()
+    {
+        return useChordPitch;
+    }
+
+    // Set the vocal chord mode for pitch computation.  Default is off.
+    function setChordPitch(
+        useChordPitch_)
+    {
+        useChordPitch = useChordPitch_;
+    }
+
+    // Get the quality setting.
+    function getQuality()
+    {
+        return quality;
+    }
+
+    // Set the "quality".  Default 0 is virtually as good as 1, but very much faster.
+    function setQuality(
+        quality_)
+    {
+        quality = quality_;
+    }
+
+    // Get the scaling factor of the stream.
+    function getVolume()
+    {
+        return volume;
+    }
+
+    // Set the scaling factor of the stream.
+    function setVolume(
+        volume_)
+    {
+        volume = volume_;
+    }
+
+    // Allocate stream buffers.
+    function allocateStreamBuffers(
+        sampleRate_,
+        numChannels_)
+    {
+        minPeriod = Math.floor(sampleRate_/SONIC_MAX_PITCH);
+        maxPeriod = Math.floor(sampleRate_/SONIC_MIN_PITCH);
+        maxRequired = 2*maxPeriod;
+        inputBufferSize = maxRequired;
+        inputBuffer = new Int16Array(maxRequired*numChannels_);
+        outputBufferSize = maxRequired;
+        outputBuffer = new Int16Array(maxRequired*numChannels_);
+        pitchBufferSize = maxRequired;
+        pitchBuffer = new Int16Array(maxRequired*numChannels_);
+        downSampleBuffer = new Int16Array(maxRequired);
+        sampleRate = sampleRate_;
+        numChannels = numChannels_;
+        oldRatePosition = 0;
+        newRatePosition = 0;
+        prevPeriod = 0;
+    }
+
+    // Create a sonic stream.
+    function Sonic(
+        sampleRate,
+        numChannels)
+    {
+        allocateStreamBuffers(sampleRate, numChannels);
+        speed = 1.0;
+        pitch = 1.0;
+        volume = 1.0;
+        rate = 1.0;
+        oldRatePosition = 0;
+        newRatePosition = 0;
+        useChordPitch = false;
+        quality = 0;
+    }
+
+    // Get the sample rate of the stream.
+    function getSampleRate()
+    {
+        return sampleRate;
+    }
+
+    // Set the sample rate of the stream.  This will cause samples buffered in the stream to be lost.
+    function setSampleRate(
+        sampleRate)
+    {
+        allocateStreamBuffers(sampleRate, numChannels);
+    }
+
+    // Get the number of channels.
+    function getNumChannels()
+    {
+        return numChannels;
+    }
+
+    // Set the num channels of the stream.  This will cause samples buffered in the stream to be lost.
+    function setNumChannels(
+        numChannels)
+    {
+        allocateStreamBuffers(sampleRate, numChannels);
+    }
+
+    // Enlarge the output buffer if needed.
+    function enlargeOutputBufferIfNeeded(
+        numSamples)
+    {
+        if(numOutputSamples + numSamples > outputBufferSize) {
+            outputBufferSize += (outputBufferSize >> 1) + numSamples;
+            outputBuffer = resize(outputBuffer, outputBufferSize);
+        }
+    }
+
+    // Enlarge the input buffer if needed.
+    function enlargeInputBufferIfNeeded(
+        numSamples)
+    {
+        if(numInputSamples + numSamples > inputBufferSize) {
+            inputBufferSize += (inputBufferSize >> 1) + numSamples;
+            inputBuffer = resize(inputBuffer, inputBufferSize);
+        }
+    }
+
+    // Add the input samples to the input buffer.
+    function addShortSamplesToInputBuffer(
+        samples,
+        numSamples)
+    {
+        if(numSamples == 0) {
+            return;
+        }
+        enlargeInputBufferIfNeeded(numSamples);
+        move(inputBuffer, numInputSamples, samples, 0, numSamples);
+        numInputSamples += numSamples;
+    }
+
+    // Remove input samples that we have already processed.
+    function removeInputSamples(
+        position)
+    {
+        var remainingSamples = numInputSamples - position;
+
+        move(inputBuffer, 0, inputBuffer, position, remainingSamples);
+        numInputSamples = remainingSamples;
+    }
+
+    // Just copy from the array to the output buffer
+    function copyToOutput(
+        samples,
+        position,
+        numSamples)
+    {
+        enlargeOutputBufferIfNeeded(numSamples);
+        move(outputBuffer, numOutputSamples, samples, position, numSamples);
+        numOutputSamples += numSamples;
+    }
+
+    // Just copy from the input buffer to the output buffer.  Return num samples copied.
+    function copyInputToOutput(
+        position)
+    {
+        var numSamples = remainingInputToCopy;
+
+        if(numSamples > maxRequired) {
+            numSamples = maxRequired;
+        }
+        copyToOutput(inputBuffer, position, numSamples);
+        remainingInputToCopy -= numSamples;
+        return numSamples;
+    }
+
+
+    // Read short data out of the stream.  Sometimes no data will be available, and zero
+    // is returned, which is not an error condition.
+    function readShortFromStream() //已改成直接返回所有的Int16Array
+    {
+        var numSamples = numOutputSamples;
+		var samples=new Int16Array(numSamples);
+        var remainingSamples = 0;
+
+        if(numSamples == 0) {
+            return samples;
+        }
+        move(samples, 0, outputBuffer, 0, numSamples);
+        move(outputBuffer, 0, outputBuffer, numSamples, remainingSamples);
+        numOutputSamples = remainingSamples;
+        return samples;
+    }
+
+    // Force the sonic stream to generate output using whatever data it currently
+    // has.  No extra delay will be added to the output, but flushing in the middle of
+    // words could introduce distortion.
+    function flushStream()
+    {
+        var remainingSamples = numInputSamples;
+        var s = speed/pitch;
+        var r = rate*pitch;
+        var expectedOutputSamples = Math.floor(numOutputSamples + Math.floor((remainingSamples/s + numPitchSamples)/r + 0.5));
+
+        // Add enough silence to flush both input and pitch buffers.
+        enlargeInputBufferIfNeeded(remainingSamples + 2*maxRequired);
+        for(var xSample = 0; xSample < 2*maxRequired*numChannels; xSample++) {
+            inputBuffer[remainingSamples*numChannels + xSample] = 0;
+        }
+        numInputSamples += 2*maxRequired;
+        writeShortToStream(null, 0);
+        // Throw away any extra samples we generated due to the silence we added.
+        if(numOutputSamples > expectedOutputSamples) {
+            numOutputSamples = expectedOutputSamples;
+        }
+        // Empty input and pitch buffers.
+        numInputSamples = 0;
+        remainingInputToCopy = 0;
+        numPitchSamples = 0;
+    }
+
+    // Return the number of samples in the output buffer
+    function samplesAvailable()
+    {
+        return numOutputSamples;
+    }
+
+    // If skip is greater than one, average skip samples together and write them to
+    // the down-sample buffer.  If numChannels is greater than one, mix the channels
+    // together as we down sample.
+    function downSampleInput(
+        samples,
+        position,
+        skip)
+    {
+        var numSamples = Math.floor(maxRequired/skip);
+        var samplesPerValue = numChannels*skip;
+        var value;
+
+        position *= numChannels;
+        for(var i = 0; i < numSamples; i++) {
+            value = 0;
+            for(var j = 0; j < samplesPerValue; j++) {
+                value += samples[position + i*samplesPerValue + j];
+            }
+            value = Math.floor(value/samplesPerValue);
+            downSampleBuffer[i] = value;
+        }
+    }
+
+    // Find the best frequency match in the range, and given a sample skip multiple.
+    // For now, just find the pitch of the first channel.
+    function findPitchPeriodInRange(
+        samples,
+        position,
+        minPeriod,
+        maxPeriod)
+    {
+        var bestPeriod = 0, worstPeriod = 255;
+        var minDiff_ = 1, maxDiff_ = 0;
+
+        position *= numChannels;
+        for(var period = minPeriod; period <= maxPeriod; period++) {
+            var diff = 0;
+            for(var i = 0; i < period; i++) {
+                var sVal = samples[position + i];
+                var pVal = samples[position + period + i];
+                diff += sVal >= pVal? sVal - pVal : pVal - sVal;
+            }
+            /* Note that the highest number of samples we add into diff will be less
+               than 256, since we skip samples.  Thus, diff is a 24 bit number, and
+               we can safely multiply by numSamples without overflow */
+            if(diff*bestPeriod < minDiff_*period) {
+                minDiff_ = diff;
+                bestPeriod = period;
+            }
+            if(diff*worstPeriod > maxDiff_*period) {
+                maxDiff_ = diff;
+                worstPeriod = period;
+            }
+        }
+        minDiff = Math.floor(minDiff_/bestPeriod);
+        maxDiff = Math.floor(maxDiff_/worstPeriod);
+
+        return bestPeriod;
+    }
+
+    // At abrupt ends of voiced words, we can have pitch periods that are better
+    // approximated by the previous pitch period estimate.  Try to detect this case.
+    function prevPeriodBetter(
+        minDiff,
+        maxDiff,
+        preferNewPeriod)
+    {
+        if(minDiff == 0 || prevPeriod == 0) {
+            return false;
+        }
+        if(preferNewPeriod) {
+            if(maxDiff > minDiff*3) {
+                // Got a reasonable match this period
+                return false;
+            }
+            if(minDiff*2 <= prevMinDiff*3) {
+                // Mismatch is not that much greater this period
+                return false;
+            }
+        } else {
+            if(minDiff <= prevMinDiff) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // Find the pitch period.  This is a critical step, and we may have to try
+    // multiple ways to get a good answer.  This version uses AMDF.  To improve
+    // speed, we down sample by an integer factor get in the 11KHz range, and then
+    // do it again with a narrower frequency range without down sampling
+    function findPitchPeriod(
+        samples,
+        position,
+        preferNewPeriod)
+    {
+        var period, retPeriod;
+        var skip = 1;
+
+        if(sampleRate > SONIC_AMDF_FREQ && quality == 0) {
+            skip = Math.floor(sampleRate/SONIC_AMDF_FREQ);
+        }
+        if(numChannels == 1 && skip == 1) {
+            period = findPitchPeriodInRange(samples, position, minPeriod, maxPeriod);
+        } else {
+            downSampleInput(samples, position, skip);
+            period = findPitchPeriodInRange(downSampleBuffer, 0, Math.floor(minPeriod/skip),
+                Math.floor(maxPeriod/skip));
+            if(skip != 1) {
+                period *= skip;
+                var minP = period - (skip << 2);
+                var maxP = period + (skip << 2);
+                if(minP < minPeriod) {
+                    minP = minPeriod;
+                }
+                if(maxP > maxPeriod) {
+                    maxP = maxPeriod;
+                }
+                if(numChannels == 1) {
+                    period = findPitchPeriodInRange(samples, position, minP, maxP);
+                } else {
+                    downSampleInput(samples, position, 1);
+                    period = findPitchPeriodInRange(downSampleBuffer, 0, minP, maxP);
+                }
+            }
+        }
+        if(prevPeriodBetter(minDiff, maxDiff, preferNewPeriod)) {
+            retPeriod = prevPeriod;
+        } else {
+            retPeriod = period;
+        }
+        prevMinDiff = minDiff;
+        prevPeriod = period;
+        return retPeriod;
+    }
+
+    // Overlap two sound segments, ramp the volume of one down, while ramping the
+    // other one from zero up, and add them, storing the result at the output.
+    function overlapAdd(
+        numSamples,
+        numChannels,
+        out,
+        outPos,
+        rampDown,
+        rampDownPos,
+        rampUp,
+        rampUpPos)
+    {
+         for(var i = 0; i < numChannels; i++) {
+            var o = outPos*numChannels + i;
+            var u = rampUpPos*numChannels + i;
+            var d = rampDownPos*numChannels + i;
+            for(var t = 0; t < numSamples; t++) {
+                out[o] = Math.floor((rampDown[d]*(numSamples - t) + rampUp[u]*t)/numSamples);
+                o += numChannels;
+                d += numChannels;
+                u += numChannels;
+            }
+        }
+    }
+
+    // Overlap two sound segments, ramp the volume of one down, while ramping the
+    // other one from zero up, and add them, storing the result at the output.
+    function overlapAddWithSeparation(
+        numSamples,
+        numChannels,
+        separation,
+        out,
+        outPos,
+        rampDown,
+        rampDownPos,
+        rampUp,
+        rampUpPos)
+    {
+        for(var i = 0; i < numChannels; i++) {
+            var o = outPos*numChannels + i;
+            var u = rampUpPos*numChannels + i;
+            var d = rampDownPos*numChannels + i;
+            for(var t = 0; t < numSamples + separation; t++) {
+                if(t < separation) {
+                    out[o] = Math.floor(rampDown[d]*(numSamples - t)/numSamples);
+                    d += numChannels;
+                } else if(t < numSamples) {
+                    out[o] = Math.floor((rampDown[d]*(numSamples - t) + rampUp[u]*(t - separation))/numSamples);
+                    d += numChannels;
+                    u += numChannels;
+                } else {
+                    out[o] = Math.floor(rampUp[u]*(t - separation)/numSamples);
+                    u += numChannels;
+                }
+                o += numChannels;
+            }
+        }
+    }
+
+    // Just move the new samples in the output buffer to the pitch buffer
+    function moveNewSamplesToPitchBuffer(
+        originalNumOutputSamples)
+    {
+        var numSamples = numOutputSamples - originalNumOutputSamples;
+
+        if(numPitchSamples + numSamples > pitchBufferSize) {
+            pitchBufferSize += (pitchBufferSize >> 1) + numSamples;
+            pitchBuffer = resize(pitchBuffer, pitchBufferSize);
+        }
+        move(pitchBuffer, numPitchSamples, outputBuffer, originalNumOutputSamples, numSamples);
+        numOutputSamples = originalNumOutputSamples;
+        numPitchSamples += numSamples;
+    }
+
+    // Remove processed samples from the pitch buffer.
+    function removePitchSamples(
+        numSamples)
+    {
+        if(numSamples == 0) {
+            return;
+        }
+        move(pitchBuffer, 0, pitchBuffer, numSamples, numPitchSamples - numSamples);
+        numPitchSamples -= numSamples;
+    }
+
+    // Change the pitch.  The latency this introduces could be reduced by looking at
+    // past samples to determine pitch, rather than future.
+    function adjustPitch(
+        originalNumOutputSamples)
+    {
+        var period, newPeriod, separation;
+        var position = 0;
+
+        if(numOutputSamples == originalNumOutputSamples) {
+            return;
+        }
+        moveNewSamplesToPitchBuffer(originalNumOutputSamples);
+        while(numPitchSamples - position >= maxRequired) {
+            period = findPitchPeriod(pitchBuffer, position, false);
+            newPeriod = Math.floor(period/pitch);
+            enlargeOutputBufferIfNeeded(newPeriod);
+            if(pitch >= 1.0) {
+                overlapAdd(newPeriod, numChannels, outputBuffer, numOutputSamples, pitchBuffer,
+                        position, pitchBuffer, position + period - newPeriod);
+            } else {
+                separation = newPeriod - period;
+                overlapAddWithSeparation(period, numChannels, separation, outputBuffer, numOutputSamples,
+                        pitchBuffer, position, pitchBuffer, position);
+            }
+            numOutputSamples += newPeriod;
+            position += period;
+        }
+        removePitchSamples(position);
+    }
+
+    // Aproximate the sinc function times a Hann window from the sinc table.
+    function findSincCoefficient(i, ratio, width) {
+        var lobePoints = Math.floor((SINC_TABLE_SIZE-1)/SINC_FILTER_POINTS);
+        var left = Math.floor(i*lobePoints + (ratio*lobePoints)/width);
+        var right = left + 1;
+        var position = i*lobePoints*width + ratio*lobePoints - left*width;
+        var leftVal = sincTable[left];
+        var rightVal = sincTable[right];
+
+        return Math.floor(((leftVal*(width - position) + rightVal*position) << 1)/width);
+    }
+
+    // Return 1 if value >= 0, else -1.  This represents the sign of value.
+    function getSign(value) {
+        return value >= 0? 1 : -1;
+    }
+
+    // Interpolate the new output sample.
+    function interpolate(
+        in_,
+        inPos,  // Index to first sample which already includes channel offset.
+        oldSampleRate,
+        newSampleRate)
+    {
+        // Compute N-point sinc FIR-filter here.  Clip rather than overflow.
+        var i;
+        var total = 0;
+        var position = newRatePosition*oldSampleRate;
+        var leftPosition = oldRatePosition*newSampleRate;
+        var rightPosition = (oldRatePosition + 1)*newSampleRate;
+        var ratio = rightPosition - position - 1;
+        var width = rightPosition - leftPosition;
+        var weight, value;
+        var oldSign;
+        var overflowCount = 0;
+
+        for (i = 0; i < SINC_FILTER_POINTS; i++) {
+            weight = findSincCoefficient(i, ratio, width);
+            /* printf("%u %f\n", i, weight); */
+            value = in_[inPos + i*numChannels]*weight;
+            oldSign = getSign(total);
+            total += value;
+            if (oldSign != getSign(total) && getSign(value) == oldSign) {
+                /* We must have overflowed.  This can happen with a sinc filter. */
+                overflowCount += oldSign;
+            }
+        }
+        /* It is better to clip than to wrap if there was a overflow. */
+        if (overflowCount > 0) {
+            return 0x7FFF;
+        } else if (overflowCount < 0) {
+            return -0x8000;
+        }
+        return (total >> 16)&0xffff;
+    }
+
+    // Change the rate.
+    function adjustRate(
+        rate,
+        originalNumOutputSamples)
+    {
+        var newSampleRate = Math.floor(sampleRate/rate);
+        var oldSampleRate = sampleRate;
+        var position;
+
+        // Set these values to help with the integer math
+        while(newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) {
+            newSampleRate >>= 1;
+            oldSampleRate >>= 1;
+        }
+        if(numOutputSamples == originalNumOutputSamples) {
+            return;
+        }
+        moveNewSamplesToPitchBuffer(originalNumOutputSamples);
+        // Leave at least one pitch sample in the buffer
+        for(position = 0; position < numPitchSamples - 1; position++) {
+            while((oldRatePosition + 1)*newSampleRate > newRatePosition*oldSampleRate) {
+                enlargeOutputBufferIfNeeded(1);
+                for(var i = 0; i < numChannels; i++) {
+                    outputBuffer[numOutputSamples*numChannels + i] = interpolate(pitchBuffer,
+                            position*numChannels + i, oldSampleRate, newSampleRate);
+                }
+                newRatePosition++;
+                numOutputSamples++;
+            }
+            oldRatePosition++;
+            if(oldRatePosition == oldSampleRate) {
+                oldRatePosition = 0;
+                if(newRatePosition != newSampleRate) {
+                    throw new Error("Assertion failed: newRatePosition != newSampleRate\n");
+                    //assert false;
+                }
+                newRatePosition = 0;
+            }
+        }
+        removePitchSamples(position);
+    }
+
+
+    // Skip over a pitch period, and copy period/speed samples to the output
+    function skipPitchPeriod(
+        samples,
+        position,
+        speed,
+        period)
+    {
+        var newSamples;
+
+        if(speed >= 2.0) {
+            newSamples = Math.floor(period/(speed - 1.0));
+        } else {
+            newSamples = period;
+            remainingInputToCopy = Math.floor(period*(2.0 - speed)/(speed - 1.0));
+        }
+        enlargeOutputBufferIfNeeded(newSamples);
+        overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples, samples, position,
+                samples, position + period);
+        numOutputSamples += newSamples;
+        return newSamples;
+    }
+
+    // Insert a pitch period, and determine how much input to copy directly.
+    function insertPitchPeriod(
+        samples,
+        position,
+        speed,
+        period)
+    {
+        var newSamples;
+
+        if(speed < 0.5) {
+            newSamples = Math.floor(period*speed/(1.0 - speed));
+        } else {
+            newSamples = period;
+            remainingInputToCopy = Math.floor(period*(2.0*speed - 1.0)/(1.0 - speed));
+        }
+        enlargeOutputBufferIfNeeded(period + newSamples);
+        move(outputBuffer, numOutputSamples, samples, position, period);
+        overlapAdd(newSamples, numChannels, outputBuffer, numOutputSamples + period, samples,
+                position + period, samples, position);
+        numOutputSamples += period + newSamples;
+        return newSamples;
+    }
+
+    // Resample as many pitch periods as we have buffered on the input.  Return 0 if
+    // we fail to resize an input or output buffer.  Also scale the output by the volume.
+    function changeSpeed(
+        speed)
+    {
+        var numSamples = numInputSamples;
+        var position = 0, period, newSamples;
+
+        if(numInputSamples < maxRequired) {
+            return;
+        }
+        do {
+            if(remainingInputToCopy > 0) {
+                newSamples = copyInputToOutput(position);
+                position += newSamples;
+            } else {
+                period = findPitchPeriod(inputBuffer, position, true);
+                if(speed > 1.0) {
+                    newSamples = skipPitchPeriod(inputBuffer, position, speed, period);
+                    position += period + newSamples;
+                } else {
+                    newSamples = insertPitchPeriod(inputBuffer, position, speed, period);
+                    position += newSamples;
+                }
+            }
+        } while(position + maxRequired <= numSamples);
+        removeInputSamples(position);
+    }
+
+    // Resample as many pitch periods as we have buffered on the input.  Scale the output by the volume.
+    function processStreamInput()
+    {
+        var originalNumOutputSamples = numOutputSamples;
+        var s = speed/pitch;
+        var r = rate;
+
+        if(!useChordPitch) {
+            r *= pitch;
+        }
+        if(s > 1.00001 || s < 0.99999) {
+            changeSpeed(s);
+        } else {
+            copyToOutput(inputBuffer, 0, numInputSamples);
+            numInputSamples = 0;
+        }
+        if(useChordPitch) {
+            if(pitch != 1.0) {
+                adjustPitch(originalNumOutputSamples);
+            }
+        } else if(r != 1.0) {
+            adjustRate(r, originalNumOutputSamples);
+        }
+        if(volume != 1.0) {
+            // Adjust output volume.
+            scaleSamples(outputBuffer, originalNumOutputSamples, numOutputSamples - originalNumOutputSamples,
+                volume);
+        }
+    }
+
+    // Write the data to the input stream, and process it.
+    function writeShortToStream(
+        samples)
+    {
+        addShortSamplesToInputBuffer(samples, samples?samples.length:0);
+        processStreamInput();
+    }
+
+	
+	
+	
+	/**导出Sonic对象**/
+	FnObj.setPitch=setPitch;
+	FnObj.setRate=setRate;
+	FnObj.setSpeed=setSpeed;
+	FnObj.setVolume=setVolume;
+	FnObj.setChordPitch=setChordPitch;
+	FnObj.setQuality=setQuality;
+	return {
+		New:Sonic
+		
+		,flushStream:flushStream
+		,writeShortToStream:writeShortToStream
+		,readShortFromStream:readShortFromStream
+	};
+}
+
+return new fn(SonicFunction_set);
+};
+
+
+
+
+
+
+
+
+
+
+Recorder.Sonic=SonicFunction;
+
+//Worker异步化
+var sonicWorker;
+Recorder.BindDestroy("sonicWorker",function(){
+	if(sonicWorker){
+		Recorder.CLog("sonicWorker Destroy");
+		sonicWorker&&sonicWorker.terminate();
+		sonicWorker=null;
+	};
+});
+//开启异步,如果返回null代表不支持,开启成功后必须调用flush方法,否则会内存泄露
+var openList={id:0};
+SonicFunction.Async=function(set){
+	if(!HasWebWorker){
+		Recorder.CLog($T("Ikdz::当前环境不支持Web Worker,不支持调用Sonic.Async"),3);
+		return null;
+	};
+	var worker=sonicWorker;
+	try{
+		var onmsg=function(e){
+			var ed=e.data;
+			var cur=wk_ctxs[ed.id];
+			if(ed.action=="init"){
+				wk_ctxs[ed.id]={
+					sampleRate:ed.sampleRate
+					
+					,sonicObj:wk_sonic({sampleRate:ed.sampleRate})
+				};
+			}else if(!cur){
+				return;
+			};
+			
+			switch(ed.action){
+			case "flush":
+				var pcm=cur.sonicObj.flush();
+				self.postMessage({
+					action:ed.action
+					,id:ed.id
+					,call:ed.call
+					,pcm:pcm
+				});
+				
+				cur.sonicObj=null;
+				delete wk_ctxs[ed.id];
+				break;
+			case "input":
+				var pcm=cur.sonicObj.input(ed.pcm);
+				self.postMessage({
+					action:ed.action
+					,id:ed.id
+					,call:ed.call
+					,pcm:pcm
+				});
+				break;
+			default:
+				if(/^set/.test(ed.action)){
+					cur.sonicObj[ed.action](ed.param);
+				};
+			};
+		};
+		if(!worker){
+			//创建一个新Worker
+			var jsCode=");var wk_ctxs={};self.onmessage="+onmsg;
+			
+			var sonicCode=Recorder.Sonic.toString();
+			var url=(window.URL||webkitURL).createObjectURL(new Blob(["var wk_sonic=(",sonicCode,jsCode], {type:"text/javascript"}));
+			
+			worker=new Worker(url);
+			setTimeout(function(){
+				(window.URL||webkitURL).revokeObjectURL(url);//必须要释放,不然每次调用内存都明显泄露内存
+			},10000);//chrome 83 file协议下如果直接释放,将会使WebWorker无法启动
+			
+			worker.onmessage=function(e){
+				var ctx=openList[e.data.id];
+				if(ctx){
+					var fn=ctx.cbs[e.data.call];
+					fn&&fn(e.data);
+				};
+			};
+		};
+		
+		var ctx=new sonicWorkerCtx(worker,set);
+		ctx.id=++openList.id;
+		openList[ctx.id]=ctx;
+		
+		worker.postMessage({
+			action:"init"
+			,id:ctx.id
+			,sampleRate:set.sampleRate
+			
+			,x:new Int16Array(5)//低版本浏览器不支持序列化TypedArray
+		});
+		
+		
+		sonicWorker=worker;
+		return ctx;
+	}catch(e){//出错了就不要提供了
+		worker&&worker.terminate();
+		
+		console.error(e);
+		return null;
+	};
+};
+
+var sonicWorkerCtx=function(worker,set){
+	this.worker=worker;
+	this.set=set;
+	
+	this.cbs={i:0};
+};
+sonicWorkerCtx.prototype={
+	cb:function(call){
+		var This=this;
+		var id="cb"+(++This.cbs.i);
+		This.cbs[id]=function(data){
+			delete This.cbs[id];
+			call(data);
+		};
+		return id;
+	}
+	,flush:function(call){
+		var This=this;if(!This.worker)return;
+		
+		This.worker.postMessage({
+			action:"flush"
+			,id:This.id
+			,call:This.cb(function(data){
+				call&&call(data.pcm);
+				
+				This.worker=null;
+				delete openList[This.id];
+				
+				//疑似泄露检测 排除id
+				var opens=-1;
+				for(var k in openList){
+					opens++;
+				};
+				if(opens){
+					Recorder.CLog($T("IC5Y::sonic worker剩{1}个未flush",0,opens),3);
+				};
+			})
+		});
+	}
+	,input:function(pcm,call){
+		var This=this;if(!This.worker)return;
+		
+		This.worker.postMessage({
+			action:"input"
+			,id:This.id
+			,pcm:pcm
+			,call:This.cb(function(data){
+				call&&call(data.pcm);
+			})
+		});
+	}
+};
+var addWorkerCtxSet=function(key){
+	sonicWorkerCtx.prototype[key]=function(param){
+		var This=this;if(!This.worker)return;
+		
+		This.worker.postMessage({
+			action:key
+			,id:This.id
+			,param:param
+		});
+	}
+};
+addWorkerCtxSet("setPitch");
+addWorkerCtxSet("setRate");
+addWorkerCtxSet("setSpeed");
+addWorkerCtxSet("setVolume");
+addWorkerCtxSet("setChordPitch");
+addWorkerCtxSet("setQuality");
+
+}));

+ 278 - 0
src/extensions/wavesurfer.view.js

@@ -0,0 +1,278 @@
+/*
+录音 Recorder扩展,音频可视化波形显示
+
+https://github.com/xiangyuecn/Recorder
+
+外观和名称来源于:
+https://github.com/katspaugh/wavesurfer.js https://github.com/collab-project/videojs-record
+
+本扩展的波形绘制直接简单的使用PCM的采样数值大小来进行线条的绘制,同一段音频绘制出的波形和Audition内显示的波形外观上几乎没有差异。
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var WaveSurferView=function(set){
+	return new fn(set);
+};
+var ViewTxt="WaveSurferView";
+var fn=function(set){
+	var This=this;
+	var o={
+		/*
+		elem:"css selector" //自动显示到dom,并以此dom大小为显示大小
+			//或者配置显示大小,手动把surferObj.elem显示到别的地方
+		,width:0 //显示宽度
+		,height:0 //显示高度
+		
+H5环境以上配置二选一
+		
+		compatibleCanvas: CanvasObject //提供一个兼容H5的canvas对象,需支持getContext("2d"),支持设置width、height,支持drawImage(canvas,...)
+		,compatibleCanvas_2x: CanvasObject //提供一个宽度是compatibleCanvas的2倍canvas对象
+		,width:0 //canvas显示宽度
+		,height:0 //canvas显示高度
+非H5环境使用以上配置
+		*/
+		
+		scale:2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
+		
+		,fps:50 //绘制帧率,不可过高,50-60fps运动性质动画明显会流畅舒适,实际显示帧率达不到这个值也并无太大影响
+		
+		,duration:2500 //当前视图窗口内最大绘制的波形的持续时间,此处决定了移动速率
+		,direction:1 //波形前进方向,取值:1由左往右,-1由右往左
+		,position:0 //绘制位置,取值-1到1,-1为最底下,0为中间,1为最顶上,小数为百分比
+		
+		,centerHeight:1 //中线基础粗细,如果为0不绘制中线,position=±1时应当设为0
+		
+		//波形颜色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
+		,linear:[0,"rgba(0,187,17,1)",0.7,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"]
+		,centerColor:"" //中线css颜色,留空取波形第一个渐变颜色
+	};
+	for(var k in set){
+		o[k]=set[k];
+	};
+	This.set=set=o;
+	
+	var cCanvas="compatibleCanvas";
+	if(set[cCanvas]){
+		var canvas=This.canvas=set[cCanvas];
+		var canvas2=This.canvas2=set[cCanvas+"_2x"];
+	}else{
+		if(!isBrowser)throw new Error($T.G("NonBrowser-1",[ViewTxt]));
+		var elem=set.elem;
+		if(elem){
+			if(typeof(elem)=="string"){
+				elem=document.querySelector(elem);
+			}else if(elem.length){
+				elem=elem[0];
+			};
+		};
+		if(elem){
+			set.width=elem.offsetWidth;
+			set.height=elem.offsetHeight;
+		};
+		
+		var thisElem=This.elem=document.createElement("div");
+		thisElem.style.fontSize=0;
+		thisElem.innerHTML='<canvas style="width:100%;height:100%;"/>';
+		
+		var canvas=This.canvas=thisElem.querySelector("canvas");
+		var canvas2=This.canvas2=document.createElement("canvas");
+		
+		if(elem){
+			elem.innerHTML="";
+			elem.appendChild(thisElem);
+		};
+	};
+	var scale=set.scale;
+	var width=set.width*scale;
+	var height=set.height*scale;
+	if(!width || !height){
+		throw new Error($T.G("IllegalArgs-1",[ViewTxt+" width=0 height=0"]));
+	};
+	
+	canvas.width=width;
+	canvas.height=height;
+	var ctx=This.ctx=canvas.getContext("2d");
+	
+	canvas2.width=width*2;//卷轴,后台绘制画布能容纳两块窗口内容,进行无缝滚动
+	canvas2.height=height;
+	var ctx2=This.ctx2=canvas2.getContext("2d");
+	
+	This.x=0;
+};
+fn.prototype=WaveSurferView.prototype={
+	genLinear:function(ctx,colors,from,to){
+		var rtv=ctx.createLinearGradient(0,from,0,to);
+		for(var i=0;i<colors.length;){
+			rtv.addColorStop(colors[i++],colors[i++]);
+		};
+		return rtv;
+	}
+	,input:function(pcmData,powerLevel,sampleRate){
+		var This=this;
+		This.sampleRate=sampleRate;
+		This.pcmData=pcmData;
+		This.pcmPos=0;
+		
+		This.inputTime=Date.now();
+		This.schedule();
+	}
+	,schedule:function(){
+		var This=this,set=This.set;
+		var interval=Math.floor(1000/set.fps);
+		if(!This.timer){
+			This.timer=setInterval(function(){
+				This.schedule();
+			},interval);
+		};
+		
+		var now=Date.now();
+		var drawTime=This.drawTime||0;
+		if(now-drawTime<interval){
+			//没到间隔时间,不绘制
+			return;
+		};
+		This.drawTime=now;
+		
+		//切分当前需要的绘制数据
+		var bufferSize=This.sampleRate/set.fps;
+		var pcm=This.pcmData;
+		var pos=This.pcmPos;
+		var arr=new Int16Array(Math.min(bufferSize,pcm.length-pos));
+		for(var i=0;i<arr.length;i++,pos++){
+			arr[i]=pcm[pos];
+		};
+		This.pcmPos=pos;
+		
+		//推入绘制
+		if(arr.length){
+			This.draw(arr,This.sampleRate);
+		}else{
+			if(now-This.inputTime>1300){
+				//超时没有输入,干掉定时器
+				clearInterval(This.timer);
+				This.timer=0;
+			};
+		};
+	}
+	,draw:function(pcmData,sampleRate){
+		var This=this,set=This.set;
+		var ctx=This.ctx2;
+		var scale=set.scale;
+		var width=set.width*scale;
+		var width2=width*2;
+		var height=set.height*scale;
+		var lineWidth=1*scale;//一条线占用1个单位长度
+		
+		//计算高度位置
+		var position=set.position;
+		var posAbs=Math.abs(set.position);
+		var originY=position==1?0:height;//y轴原点
+		var heightY=height;//最高的一边高度
+		if(posAbs<1){
+			heightY=heightY/2;
+			originY=heightY;
+			heightY=Math.floor(heightY*(1+posAbs));
+			originY=Math.floor(position>0?originY*(1-posAbs):originY*(1+posAbs));
+		};
+		
+		//计算绘制占用长度
+		var pcmDuration=pcmData.length*1000/sampleRate;
+		var pcmWidth=pcmDuration*width/set.duration;
+		pcmWidth+=This.drawLoss||0;
+		var pointCount=0;
+		if(pcmWidth<lineWidth){
+			This.drawLoss=pcmWidth;
+			//pointCount=0; 不够一根不绘制
+		}else{
+			This.drawLoss=0;
+			pointCount=Math.floor(pcmWidth/lineWidth);
+		};
+		
+		//***后台卷轴连续绘制***
+		var linear1=This.genLinear(ctx,set.linear,originY,originY-heightY);//上半部分的填充
+		var linear2=This.genLinear(ctx,set.linear,originY,originY+heightY);//下半部分的填充
+		
+		var x=This.x;
+		var step=pcmData.length/pointCount;
+		for(var i=0,idx=0;i<pointCount;i++){
+			var j=Math.floor(idx);
+			var end=Math.floor(idx+step);
+			idx+=step;
+			
+			//寻找区间内最大值
+			var max=0;
+			for(;j<end;j++){
+				max=Math.max(max,Math.abs(pcmData[j]));
+			};
+			
+			//计算高度
+			var h=heightY*Math.min(1,max/0x7fff);
+			
+			//绘制当前线条,不管方向,从x:0往x:max方向画就是了
+			//绘制上半部分
+			if(originY!=0){
+				ctx.fillStyle=linear1;
+				ctx.fillRect(x, originY-h, lineWidth, h);
+			};
+			//绘制下半部分
+			if(originY!=height){
+				ctx.fillStyle=linear2;
+				ctx.fillRect(x, originY, lineWidth, h);
+			};
+			
+			x+=lineWidth;
+			//超过卷轴宽度,移动画布第二个窗口内容到第一个窗口
+			if(x>=width2){
+				ctx.clearRect(0,0,width,height);
+				ctx.drawImage(This.canvas2,width,0,width,height,0,0,width,height);
+				ctx.clearRect(width,0,width,height);
+				x=width;
+			};
+		};
+		This.x=x;
+		
+		//***画回到显示区域***
+		ctx=This.ctx;
+		ctx.clearRect(0,0,width,height);
+		
+		//绘制一条中线
+		var centerHeight=set.centerHeight*scale;
+		if(centerHeight){
+			var y=originY-Math.floor(centerHeight/2);
+			y=Math.max(y,0);
+			y=Math.min(y,height-centerHeight);
+			
+			ctx.fillStyle=set.centerColor||set.linear[1];
+			ctx.fillRect(0, y, width, centerHeight);
+		};
+		
+		//画回画布
+		var srcX=0,srcW=x,destX=0;
+		if(srcW>width){
+			srcX=srcW-width;
+			srcW=width;
+		}else{
+			destX=width-srcW;
+		};
+		
+		var direction=set.direction;
+		if(direction==-1){//由右往左
+			ctx.drawImage(This.canvas2,srcX,0,srcW,height,destX,0,srcW,height);
+		}else{//由左往右
+			ctx.save();
+			ctx.scale(-1,1);
+			ctx.drawImage(This.canvas2,srcX,0,srcW,height,-width+destX,0,srcW,height);
+			ctx.restore();
+		};
+	}
+};
+Recorder[ViewTxt]=WaveSurferView;
+
+	
+}));

+ 229 - 0
src/extensions/waveview.js

@@ -0,0 +1,229 @@
+/*
+录音 Recorder扩展,动态波形显示
+https://github.com/xiangyuecn/Recorder
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	var rec=win.Recorder,ni=rec.i18n;
+	factory(rec,ni,ni.$T,browser);
+}(function(Recorder,i18n,$T,isBrowser){
+"use strict";
+
+var WaveView=function(set){
+	return new fn(set);
+};
+var ViewTxt="WaveView";
+var fn=function(set){
+	var This=this;
+	var o={
+		/*
+		elem:"css selector" //自动显示到dom,并以此dom大小为显示大小
+			//或者配置显示大小,手动把waveviewObj.elem显示到别的地方
+		,width:0 //显示宽度
+		,height:0 //显示高度
+		
+H5环境以上配置二选一
+		
+		compatibleCanvas: CanvasObject //提供一个兼容H5的canvas对象,需支持getContext("2d"),支持设置width、height,支持drawImage(canvas,...)
+		,width:0 //canvas显示宽度
+		,height:0 //canvas显示高度
+非H5环境使用以上配置
+		*/
+		
+		scale:2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
+		,speed:9 //移动速度系数,越大越快
+		,phase:21.8 //相位,调整了速度后,调整这个值得到一个看起来舒服的波形
+		
+		,fps:20 //绘制帧率,调整后也需调整phase值
+		,keep:true //当停止了input输入时,是否保持波形,设为false停止后将变成一条线
+		
+		,lineWidth:3 //线条基础粗细
+		
+		//渐变色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
+		,linear1:[0,"rgba(150,96,238,1)",0.2,"rgba(170,79,249,1)",1,"rgba(53,199,253,1)"] //线条渐变色1,从左到右
+		,linear2:[0,"rgba(209,130,255,0.6)",1,"rgba(53,199,255,0.6)"] //线条渐变色2,从左到右
+		,linearBg:[0,"rgba(255,255,255,0.2)",1,"rgba(54,197,252,0.2)"] //背景渐变色,从上到下
+	};
+	for(var k in set){
+		o[k]=set[k];
+	};
+	This.set=set=o;
+	
+	var cCanvas="compatibleCanvas";
+	if(set[cCanvas]){
+		var canvas=This.canvas=set[cCanvas];
+	}else{
+		if(!isBrowser)throw new Error($T.G("NonBrowser-1",[ViewTxt]));
+		var elem=set.elem;
+		if(elem){
+			if(typeof(elem)=="string"){
+				elem=document.querySelector(elem);
+			}else if(elem.length){
+				elem=elem[0];
+			};
+		};
+		if(elem){
+			set.width=elem.offsetWidth;
+			set.height=elem.offsetHeight;
+		};
+		
+		var thisElem=This.elem=document.createElement("div");
+		thisElem.style.fontSize=0;
+		thisElem.innerHTML='<canvas style="width:100%;height:100%;"/>';
+		
+		var canvas=This.canvas=thisElem.querySelector("canvas");
+		
+		if(elem){
+			elem.innerHTML="";
+			elem.appendChild(thisElem);
+		};
+	};
+	var scale=set.scale;
+	var width=set.width*scale;
+	var height=set.height*scale;
+	if(!width || !height){
+		throw new Error($T.G("IllegalArgs-1",[ViewTxt+" width=0 height=0"]));
+	};
+	
+	canvas.width=width;
+	canvas.height=height;
+	var ctx=This.ctx=canvas.getContext("2d");
+	
+	This.linear1=This.genLinear(ctx,width,set.linear1);
+	This.linear2=This.genLinear(ctx,width,set.linear2);
+	This.linearBg=This.genLinear(ctx,height,set.linearBg,true);
+	
+	This._phase=0;
+};
+fn.prototype=WaveView.prototype={
+	genLinear:function(ctx,size,colors,top){
+		var rtv=ctx.createLinearGradient(0,0,top?0:size,top?size:0);
+		for(var i=0;i<colors.length;){
+			rtv.addColorStop(colors[i++],colors[i++]);
+		};
+		return rtv;
+	}
+	,genPath:function(frequency,amplitude,phase){
+		//曲线生成算法参考 https://github.com/HaloMartin/MCVoiceWave/blob/f6dc28975fbe0f7fc6cc4dbc2e61b0aa5574e9bc/MCVoiceWave/MCVoiceWaveView.m#L268
+		var rtv=[];
+		var This=this,set=This.set;
+		var scale=set.scale;
+		var width=set.width*scale;
+		var maxAmplitude=set.height*scale/2;
+		
+		for(var x=0;x<=width;x+=scale) {
+			var scaling=(1+Math.cos(Math.PI+(x/width)*2*Math.PI))/2;
+			var y=scaling*maxAmplitude*amplitude*Math.sin(2*Math.PI*(x/width)*frequency+phase)+maxAmplitude;
+			rtv.push(y);
+		}
+		return rtv;
+	}
+	,input:function(pcmData,powerLevel,sampleRate){
+		var This=this;
+		This.sampleRate=sampleRate;
+		This.pcmData=pcmData;
+		This.pcmPos=0;
+		
+		This.inputTime=Date.now();
+		This.schedule();
+	}
+	,schedule:function(){
+		var This=this,set=This.set;
+		var interval=Math.floor(1000/set.fps);
+		if(!This.timer){
+			This.timer=setInterval(function(){
+				This.schedule();
+			},interval);
+		};
+		
+		var now=Date.now();
+		var drawTime=This.drawTime||0;
+		if(now-drawTime<interval){
+			//没到间隔时间,不绘制
+			return;
+		};
+		This.drawTime=now;
+		
+		//切分当前需要的绘制数据
+		var bufferSize=This.sampleRate/set.fps;
+		var pcm=This.pcmData;
+		var pos=This.pcmPos;
+		var len=Math.max(0, Math.min(bufferSize,pcm.length-pos));
+		var sum=0;
+		for(var i=0;i<len;i++,pos++){
+			sum+=Math.abs(pcm[pos]);
+		};
+		This.pcmPos=pos;
+		
+		//推入绘制
+		if(len || !set.keep){
+			This.draw(Recorder.PowerLevel(sum, len));
+		}
+		if(!len && now-This.inputTime>1300){
+			//超时没有输入,干掉定时器
+			clearInterval(This.timer);
+			This.timer=0;
+		}
+	}
+	,draw:function(powerLevel){
+		var This=this,set=This.set;
+		var ctx=This.ctx;
+		var scale=set.scale;
+		var width=set.width*scale;
+		var height=set.height*scale;
+		
+		var speedx=set.speed/set.fps;
+		var phase=This._phase-=speedx;//位移速度
+		var phase2=phase+speedx*set.phase;
+		var amplitude=powerLevel/100;
+		var path1=This.genPath(2,amplitude,phase);
+		var path2=This.genPath(1.8,amplitude,phase2);
+		
+		//开始绘制图形
+		ctx.clearRect(0,0,width,height);
+		
+		//绘制包围背景
+		ctx.beginPath();
+		for(var i=0,x=0;x<=width;i++,x+=scale) {
+			if (x==0) {
+				ctx.moveTo(x,path1[i]);
+			}else {
+				ctx.lineTo(x,path1[i]);
+			};
+		};
+		i--;
+		for(var x=width-1;x>=0;i--,x-=scale) {
+			ctx.lineTo(x,path2[i]);
+		};
+		ctx.closePath();
+		ctx.fillStyle=This.linearBg;
+		ctx.fill();
+		
+		//绘制线
+		This.drawPath(path2,This.linear2);
+		This.drawPath(path1,This.linear1);
+	}
+	,drawPath:function(path,linear){
+		var This=this,set=This.set;
+		var ctx=This.ctx;
+		var scale=set.scale;
+		var width=set.width*scale;
+		
+		ctx.beginPath();
+		for(var i=0,x=0;x<=width;i++,x+=scale) {
+			if (x==0) {
+				ctx.moveTo(x,path[i]);
+			}else {
+				ctx.lineTo(x,path[i]);
+			};
+		};
+		ctx.lineWidth=set.lineWidth*scale;
+		ctx.strokeStyle=linear;
+		ctx.stroke();
+	}
+};
+Recorder[ViewTxt]=WaveView;
+
+	
+}));

+ 915 - 0
src/i18n/Template.js

@@ -0,0 +1,915 @@
+/*
+Recorder i18n/Template.js
+https://github.com/xiangyuecn/Recorder
+
+Usage: Recorder.i18n.lang="Your-Language-Name" or "your-language"
+
+Desc: This file is a language translation template file. After copying and renaming, translate the text into the corresponding language. 此文件为语言翻译模板文件,复制并改名后,将文本翻译成对应语言即可。
+
+注意:请勿修改//@@打头的文本行;以下代码结构由/src/package-i18n.js自动生成,只允许在字符串中填写翻译后的文本,请勿改变代码结构;翻译的文本如果需要明确的空值,请填写"=Empty";文本中的变量用{n}表示(n代表第几个变量),所有变量必须都出现至少一次,如果不要某变量用{n!}表示
+
+Note: Do not modify the text lines starting with //@@; The following code structure is automatically generated by /src/package-i18n.js, only the translated text is allowed to be filled in the string, please do not change the code structure; If the translated text requires an explicit empty value, please fill in "=Empty"; Variables in the text are represented by {n} (n represents the number of variables), all variables must appear at least once, if a variable is not required, it is represented by {n!}
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	factory(win.Recorder,browser);
+}(function(Recorder,isBrowser){
+"use strict";
+var i18n=Recorder.i18n;
+
+//@@User Code-1 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-1 End @@
+
+//@@Exec i18n.lang="Your-Language-Name";
+Recorder.CLog('Import Recorder i18n lang="Your-Language-Name"');
+
+i18n.alias["Your-Language-Name"]="your-language";
+
+var putSet={lang:"your-language"};
+
+i18n.data["rtl$your-language"]=false;
+i18n.data["desc$your-language"]="This file is a language translation template file. After copying and renaming, translate the text into the corresponding language. 此文件为语言翻译模板文件,复制并改名后,将文本翻译成对应语言即可。";
+//@@Exec i18n.GenerateDisplayEnglish=true;
+
+
+
+//*************** Begin srcFile=recorder-core.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="重复导入{1}"
+//@@en="Duplicate import {1}"
+//@@Put0
+ "K8zP:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="剩{1}个GetContext未close"
+//@@en="There are {1} GetContext unclosed"
+,"mSxV:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="(注意:ctx不是running状态,rec.open和start至少要有一个在用户操作(触摸、点击等)时进行调用,否则将在rec.start时尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)"
+//@@en=" (Note: ctx is not in the running state. At least one of rec.open and start must be called during user operations (touch, click, etc.), otherwise ctx.resume will be attempted during rec.start, which may cause compatibility issues (iOS only), please refer to the runningContext configuration in the documentation) "
+,"nMIy:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="。由于{1}内部1秒375次回调,在移动端可能会有性能问题导致回调丢失录音变短,PC端无影响,暂不建议开启{1}。"
+//@@en=". Due to 375 callbacks in 1 second in {1}, there may be performance problems on the mobile side, which may cause the callback to be lost and the recording to be shortened, but it will not affect the PC side. It is not recommended to enable {1} for now."
+,"ZGlf:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="Connect采用老的{1},"
+//@@en="Connect uses the old {1}, "
+,"7TU0:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="但已设置{1}尝试启用{2}"
+//@@en="But {1} is set trying to enable {2}"
+,"JwCL:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="可设置{1}尝试启用{2}"
+//@@en="Can set {1} try to enable {2}"
+,"VGjB:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}未返回任何音频,恢复使用{2}"
+//@@en="{1} did not return any audio, reverting to {2}"
+,"MxX1:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}多余回调"
+//@@en="{1} redundant callback"
+,"XUap:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="Connect采用{1},设置{2}可恢复老式{3}"
+//@@en="Connect uses {1}, set {2} to restore old-fashioned {3}"
+,"yOta:"+ //args: {1}-{3}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="(此浏览器不支持{1})"
+//@@en=" (This browser does not support {1}) "
+,"VwPd:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}未返回任何音频,降级使用{2}"
+//@@en="{1} did not return any audio, downgrade to {2}"
+,"vHnb:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}多余回调"
+//@@en="{1} redundant callback"
+,"O9P7:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="Connect采用{1},设置{2}可恢复使用{3}或老式{4}"
+//@@en="Connect uses {1}, set {2} to restore to using {3} or old-fashioned {4}"
+,"LMEm:"+ //args: {1}-{4}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}的filter采样率变了,重设滤波"
+//@@en="The filter sampleRate of {1} has changed, reset the filter"
+,"d48C:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}似乎传入了未重置chunk {2}"
+//@@en="{1} seems to have passed in an unreset chunk {2}"
+,"tlbC:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}和{2}必须是数值"
+//@@en="{1} and {2} must be number"
+,"VtS4:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="录音open失败:"
+//@@en="Recording open failed: "
+,"5tWi:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="open被取消"
+//@@en="open cancelled"
+,"dFm8:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="open被中断"
+//@@en="open interrupted"
+,"VtJO:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh=",可尝试使用RecordApp解决方案"
+//@@en=", you can try to use the RecordApp solution "
+,"EMJq:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="不能录音:"
+//@@en="Cannot record: "
+,"A5bm:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="不支持此浏览器从流中获取录音"
+//@@en="This browser does not support obtaining recordings from stream"
+,"1iU7:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="从流中打开录音失败:"
+//@@en="Failed to open recording from stream: "
+,"BTW2:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="无权录音(跨域,请尝试给iframe添加麦克风访问策略,如{1})"
+//@@en="No permission to record (cross domain, please try adding microphone access policy to iframe, such as: {1})"
+,"Nclz:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="用户拒绝了录音权限"
+//@@en="User denied recording permission"
+,"gyO5:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="浏览器禁止不安全页面录音,可开启https解决"
+//@@en="Browser prohibits recording of unsafe pages, which can be resolved by enabling HTTPS"
+,"oWNo:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh=",无可用麦克风"
+//@@en=", no microphone available"
+,"jBa9:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="此浏览器不支持录音"
+//@@en="This browser does not support recording"
+,"COxc:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="发现同时多次调用open"
+//@@en="It was found that open was called multiple times at the same time"
+,"upb8:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="录音功能无效:无音频流"
+//@@en="Invalid recording: no audio stream"
+,"Q1GA:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="请求录音权限错误"
+//@@en="Error requesting recording permission"
+,"xEQR:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="无法录音:"
+//@@en="Unable to record: "
+,"bDOG:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh=",未配置noiseSuppression和echoCancellation时浏览器可能会自动打开降噪和回声消除,移动端可能会降低系统播放音量(关闭录音后可恢复),请参阅文档中audioTrackSet配置"
+//@@en=", when noiseSuppression and echoCancellation are not configured, the browser may automatically enable noise suppression and echo cancellation, and the mobile terminal may reduce the system playback volume (recovery after the recording is closed), please refer to the audioTrackSet configuration in the document."
+,"RiWe:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="close被忽略(因为同时open了多个rec,只有最后一个会真正close)"
+//@@en="close is ignored (because multiple recs are open at the same time, only the last one will actually close)"
+,"hWVz:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="忽略"
+//@@en="ignore"
+,"UHvm:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="不支持{1}架构"
+//@@en="{1} architecture not supported"
+,"Essp:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}类型不支持设置takeoffEncodeChunk"
+//@@en="{1} type does not support setting takeoffEncodeChunk"
+,"2XBl:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="(未加载编码器)"
+//@@en="(Encoder not loaded)"
+,"LG7e:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}环境不支持实时处理"
+//@@en="{1} environment does not support real-time processing"
+,"7uMV:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="补偿{1}ms"
+//@@en="Compensation {1}ms"
+,"4Kfd:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="未补偿{1}ms"
+//@@en="Uncompensated {1}ms"
+,"bM5i:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="回调出错是不允许的,需保证不会抛异常"
+//@@en="Callback error is not allowed, you need to ensure that no exception will be thrown"
+,"gFUF:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="低性能,耗时{1}ms"
+//@@en="Low performance, took {1}ms"
+,"2ghS:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="未进入异步前不能清除buffers"
+//@@en="Buffers cannot be cleared before entering async"
+,"ufqH:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="start失败:未open"
+//@@en="start failed: not open"
+,"6WmN:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="start 开始录音,"
+//@@en="start recording, "
+,"kLDN:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="start被中断"
+//@@en="start was interrupted"
+,"Bp2y:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh=",可能无法录音:"
+//@@en=", may fail to record: "
+,"upkE:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="stop 和start时差:"
+//@@en="Stop and start time difference: "
+,"Xq4s:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="补偿:"
+//@@en="compensate: "
+,"3CQP:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="结束录音失败:"
+//@@en="Failed to stop recording: "
+,"u8JG:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh=",请设置{1}"
+//@@en=", please set {1}"
+,"1skY:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="结束录音 编码花{1}ms 音频时长{2}ms 文件大小{3}b"
+//@@en="Stop recording, encoding takes {1}ms, audio duration {2}ms, file size {3}b"
+,"Wv7l:"+ //args: {1}-{3}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}编码器返回的不是{2}"
+//@@en="{1} encoder returned not {2}"
+,"Vkbd:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="启用takeoffEncodeChunk后stop返回的blob长度为0不提供音频数据"
+//@@en="After enabling takeoffEncodeChunk, the length of the blob returned by stop is 0 and no audio data is provided"
+,"QWnr:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="生成的{1}无效"
+//@@en="Invalid generated {1}"
+,"Sz2H:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="未开始录音"
+//@@en="Recording not started"
+,"wf9t:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh=",开始录音前无用户交互导致AudioContext未运行"
+//@@en=", No user interaction before starting recording, resulting in AudioContext not running"
+,"Dl2c:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="未采集到录音"
+//@@en="Recording not captured"
+,"Ltz3:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="未加载{1}编码器,请尝试到{2}的src/engine内找到{1}的编码器并加载"
+//@@en="The {1} encoder is not loaded. Please try to find the {1} encoder in the src/engine directory of the {2} and load it"
+,"xGuI:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="录音错误:"
+//@@en="Recording error: "
+,"AxOH:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="音频buffers被释放"
+//@@en="Audio buffers are released"
+,"xkKd:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="采样:{1} 花:{2}ms"
+//@@en="Sampled: {1}, took: {2}ms"
+,"CxeT:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="非浏览器环境,不支持{1}"
+//@@en="Non-browser environment, does not support {1}"
+,"NonBrowser-1:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="参数错误:{1}"
+//@@en="Illegal argument: {1}"
+,"IllegalArgs-1:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="调用{1}需要先导入{2}"
+//@@en="Calling {1} needs to import {2} first"
+,"NeedImport-2:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="不支持:{1}"
+//@@en="Not support: {1}"
+,"NotSupport-1:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="覆盖导入{1}"
+//@@en="Override import {1}"
+,"8HO5:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=recorder-core.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-amr.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="AMR-NB(NarrowBand),采样率设置无效(只提供8000hz),比特率范围:{1}(默认12.2kbps),一帧20ms、{2}字节;浏览器一般不支持播放amr格式,可用Recorder.amr2wav()转码成wav播放"
+//@@en="AMR-NB (NarrowBand), sampleRate setting is invalid (only 8000hz is provided), bitRate range: {1} (default 12.2kbps), one frame 20ms, {2} bytes; browsers generally do not support playing amr format, available Recorder.amr2wav() transcoding into wav playback"
+//@@Put0
+ "b2mN:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="AMR Info: 和设置的不匹配{1},已更新成{2}"
+//@@en="AMR Info: does not match the set {1}, has been updated to {2}"
+,"tQBv:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="数据采样率低于{1}"
+//@@en="Data sampleRate lower than {1}"
+,"q12D:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+//@@en="The current browser version is too low to process in real time"
+,"TxjV:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="takeoffEncodeChunk接管AMR编码器输出的二进制数据,只有首次回调数据(首帧)包含AMR头;在合并成AMR文件时,如果没有把首帧数据包含进去,则必须在文件开头添加上AMR头:Recorder.AMR.AMR_HEADER(转成二进制),否则无法播放"
+//@@en="takeoffEncodeChunk takes over the binary data output by the AMR encoder, and only the first callback data (the first frame) contains the AMR header; when merging into an AMR file, if the first frame data is not included, the AMR header must be added at the beginning of the file: Recorder.AMR.AMR_HEADER (converted to binary), otherwise it cannot be played"
+,"Q7p7:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="当前环境不支持Web Worker,amr实时编码器运行在主线程中"
+//@@en="The current environment does not support Web Worker, and the amr real-time encoder runs in the main thread"
+,"6o9Z:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="amr worker剩{1}个未stop"
+//@@en="amr worker left {1} unstopped"
+,"yYWs:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="amr编码器未start"
+//@@en="amr encoder not started"
+,"jOi8:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=engine/beta-amr.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-ogg.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="Ogg Vorbis,比特率取值16-100kbps,采样率取值无限制"
+//@@en="Ogg Vorbis, bitRate 16-100kbps, sampleRate unlimited"
+//@@Put0
+ "O8Gn:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+//@@en="The current browser version is too low to process in real time"
+,"5si6:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="takeoffEncodeChunk接管OggVorbis编码器输出的二进制数据,Ogg由数据页组成,一页包含多帧音频数据(含几秒的音频,一页数据无法单独解码和播放),此编码器每次输出都是完整的一页数据,因此实时性会比较低;在合并成完整ogg文件时,必须将输出的所有数据合并到一起,否则可能无法播放,不支持截取中间一部分单独解码和播放"
+//@@en="takeoffEncodeChunk takes over the binary data output by the OggVorbis encoder. Ogg is composed of data pages. One page contains multiple frames of audio data (including a few seconds of audio, and one page of data cannot be decoded and played alone). This encoder outputs a complete page of data each time, so the real-time performance will be relatively low; when merging into a complete ogg file, all the output data must be merged together, otherwise it may not be able to play, and it does not support intercepting the middle part to decode and play separately"
+,"R8yz:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="当前环境不支持Web Worker,OggVorbis实时编码器运行在主线程中"
+//@@en="The current environment does not support Web Worker, and the OggVorbis real-time encoder runs in the main thread"
+,"hB9D:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="ogg worker剩{1}个未stop"
+//@@en="There are {1} unstopped ogg workers"
+,"oTiy:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="ogg编码器未start"
+//@@en="ogg encoder not started"
+,"dIpw:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=engine/beta-ogg.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-webm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="此浏览器不支持进行webm编码,未实现MediaRecorder"
+//@@en="This browser does not support webm encoding, MediaRecorder is not implemented"
+//@@Put0
+ "L49q:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="只有比较新的浏览器支持,压缩率和mp3差不多。由于未找到对已有pcm数据进行快速编码的方法,只能按照类似边播放边收听形式把数据导入到MediaRecorder,有几秒就要等几秒。输出音频虽然可以通过比特率来控制文件大小,但音频文件中的比特率并非设定比特率,采样率由于是我们自己采样的,到这个编码器随他怎么搞"
+//@@en="Only newer browsers support it, and the compression rate is similar to mp3. Since there is no way to quickly encode the existing pcm data, the data can only be imported into MediaRecorder in a similar manner while playing and listening, and it takes a few seconds to wait for a few seconds. Although the output audio can control the file size through the bitRate, the bitRate in the audio file is not the set bitRate. Since the sampleRate is sampled by ourselves, we can do whatever we want with this encoder."
+,"tsTW:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="此浏览器不支持把录音转成webm格式"
+//@@en="This browser does not support converting recordings to webm format"
+,"aG4z:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="转码webm出错:{1}"
+//@@en="Error encoding webm: {1}"
+,"PIX0:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=engine/beta-webm.js ***************
+
+
+
+//*************** Begin srcFile=engine/g711x.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="{1};{2}音频文件无法直接播放,可用Recorder.{2}2wav()转码成wav播放;采样率比特率设置无效,固定为8000hz采样率、16位,每个采样压缩成8位存储,音频文件大小为8000字节/秒;如需任意采样率支持,请使用Recorder.{2}_encode()方法"
+//@@en="{1}; {2} audio files cannot be played directly, and can be transcoded into wav by Recorder.{2}2wav(); the sampleRate bitRate setting is invalid, fixed at 8000hz sampleRate, 16 bits, each sample is compressed into 8 bits for storage, and the audio file size is 8000 bytes/second; if you need any sampleRate support, please use Recorder.{2}_encode() Method"
+//@@Put0
+ "d8YX:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="数据采样率低于{1}"
+//@@en="Data sampleRate lower than {1}"
+,"29UK:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}编码器未start"
+//@@en="{1} encoder not started"
+,"quVJ:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=engine/g711x.js ***************
+
+
+
+//*************** Begin srcFile=engine/mp3.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="采样率范围:{1};比特率范围:{2}(不同比特率支持的采样率范围不同,小于32kbps时采样率需小于32000)"
+//@@en="sampleRate range: {1}; bitRate range: {2} (the sampleRate range supported by different bitRate is different, when the bitRate is less than 32kbps, the sampleRate must be less than 32000)"
+//@@Put0
+ "Zm7L:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}不在mp3支持的取值范围:{2}"
+//@@en="{1} is not in the value range supported by mp3: {2}"
+,"eGB9:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="sampleRate已更新为{1},因为{2}不在mp3支持的取值范围:{3}"
+//@@en="sampleRate has been updated to {1}, because {2} is not in the value range supported by mp3: {3}"
+,"zLTa:"+ //args: {1}-{3}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+//@@en="The current browser version is too low to process in real time"
+,"yhUs:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="当前环境不支持Web Worker,mp3实时编码器运行在主线程中"
+//@@en="The current environment does not support Web Worker, and the mp3 real-time encoder runs in the main thread"
+,"k9PT:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="mp3 worker剩{1}个未stop"
+//@@en="There are {1} unstopped mp3 workers left"
+,"fT6M:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="mp3编码器未start"
+//@@en="mp3 encoder not started"
+,"mPxH:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="和设置的不匹配{1},已更新成{2}"
+//@@en="Does not match the set {1}, has been updated to {2}"
+,"uY9i:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="Fix移除{1}帧"
+//@@en="Fix remove {1} frame"
+,"iMSm:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="移除帧数过多"
+//@@en="Remove too many frames"
+,"b9zm:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=engine/mp3.js ***************
+
+
+
+//*************** Begin srcFile=engine/pcm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="pcm为未封装的原始音频数据,pcm音频文件无法直接播放,可用Recorder.pcm2wav()转码成wav播放;支持位数8位、16位(填在比特率里面),采样率取值无限制"
+//@@en="pcm is unencapsulated original audio data, pcm audio files cannot be played directly, and can be transcoded into wav by Recorder.pcm2wav(); it supports 8-bit and 16-bit bits (fill in the bitRate), and the sampleRate is unlimited"
+//@@Put0
+ "fWsN:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="PCM Info: 不支持{1}位,已更新成{2}位"
+//@@en="PCM Info: {1} bit is not supported, has been updated to {2} bit"
+,"uMUJ:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="pcm2wav必须提供sampleRate和bitRate"
+//@@en="pcm2wav must provide sampleRate and bitRate"
+,"KmRz:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="pcm编码器未start"
+//@@en="pcm encoder not started"
+,"sDkA:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=engine/pcm.js ***************
+
+
+
+//*************** Begin srcFile=engine/wav.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="支持位数8位、16位(填在比特率里面),采样率取值无限制;此编码器仅在pcm数据前加了一个44字节的wav头,编码出来的16位wav文件去掉开头的44字节即可得到pcm(注:其他wav编码器可能不是44字节)"
+//@@en="Supports 8-bit and 16-bit bits (fill in the bitRate), and the sampleRate is unlimited; this encoder only adds a 44-byte wav header before the pcm data, and the encoded 16-bit wav file removes the beginning 44 bytes to get pcm (note: other wav encoders may not be 44 bytes)"
+//@@Put0
+ "gPSE:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="WAV Info: 不支持{1}位,已更新成{2}位"
+//@@en="WAV Info: {1} bit is not supported, has been updated to {2} bit"
+,"wyw9:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=engine/wav.js ***************
+
+
+
+//*************** Begin srcFile=extensions/buffer_stream.player.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="getAudioSrc方法已过时:请直接使用getMediaStream然后赋值给audio.srcObject,仅允许在不支持srcObject的浏览器中调用本方法赋值给audio.src以做兼容"
+//@@en="The getAudioSrc method is obsolete: please use getMediaStream directly and then assign it to audio.srcObject, it is only allowed to call this method in browsers that do not support srcObject and assign it to audio.src for compatibility"
+//@@Put0
+ "0XYC:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="start被stop终止"
+//@@en="start is terminated by stop"
+,"6DDt:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}多次start"
+//@@en="{1} repeat start"
+,"I4h4:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="浏览器不支持打开{1}"
+//@@en="The browser does not support opening {1}"
+,"P6Gs:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="(注意:ctx不是running状态,start需要在用户操作(触摸、点击等)时进行调用,否则会尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)"
+//@@en=" (Note: ctx is not in the running state, start needs to be called when the user operates (touch, click, etc.), otherwise it will try to perform ctx.resume, which may cause compatibility issues (only iOS), please refer to the runningContext configuration in the document) "
+,"JwDm:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="此浏览器的AudioBuffer实现不支持动态特性,采用兼容模式"
+//@@en="The AudioBuffer implementation of this browser does not support dynamic features, use compatibility mode"
+,"qx6X:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="环境检测超时"
+//@@en="Environment detection timeout"
+,"cdOx:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="可能无法播放:{1}"
+//@@en="Could not play: {1}"
+,"S2Bu:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="input调用失败:非pcm[Int16,...]输入时,必须解码或者使用transform转换"
+//@@en="input call failed: non-pcm[Int16,...] input must be decoded or converted using transform"
+,"ZfGG:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="input调用失败:未提供sampleRate"
+//@@en="input call failed: sampleRate not provided"
+,"N4ke:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="input调用失败:data的sampleRate={1}和之前的={2}不同"
+//@@en="input call failed: sampleRate={1} of data is different from previous={2}"
+,"IHZd:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="{1}未调用start方法"
+//@@en="{1} did not call the start method"
+,"TZPq:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="浏览器不支持音频解码"
+//@@en="Browser does not support audio decoding"
+,"iCFC:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="音频解码数据必须是ArrayBuffer"
+//@@en="Audio decoding data must be ArrayBuffer"
+,"wE2k:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="音频解码失败:{1}"
+//@@en="Audio decoding failed: {1}"
+,"mOaT:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=extensions/buffer_stream.player.js ***************
+
+
+
+//*************** Begin srcFile=extensions/create-audio.nmn2pcm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="符号[{1}]无效:{2}"
+//@@en="Invalid symbol [{1}]: {2}"
+//@@Put0
+ "3RBa:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="音符[{1}]无效:{2}"
+//@@en="Invalid note [{1}]: {2}"
+,"U212:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="多个音时必须对齐,相差{1}ms"
+//@@en="Multiple tones must be aligned, with a difference of {1}ms"
+,"7qAD:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="祝你生日快乐"
+//@@en="Happy Birthday to You"
+,"QGsW:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="致爱丽丝"
+//@@en="For Elise"
+,"emJR:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="卡农-右手简谱"
+//@@en="Canon - Right Hand Notation"
+,"GsYy:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="卡农"
+//@@en="Canon"
+,"bSFZ:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=extensions/create-audio.nmn2pcm.js ***************
+
+
+
+//*************** Begin srcFile=extensions/sonic.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="当前环境不支持Web Worker,不支持调用Sonic.Async"
+//@@en="The current environment does not support Web Worker and does not support calling Sonic.Async"
+//@@Put0
+ "Ikdz:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="sonic worker剩{1}个未flush"
+//@@en="There are {1} unflushed sonic workers left"
+,"IC5Y:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=extensions/sonic.js ***************
+
+
+
+//*************** Begin srcFile=app-support/app-native-support.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="{1}中的{2}方法未实现,请在{3}文件中或配置文件中实现此方法"
+//@@en="The {2} method in {1} is not implemented, please implement this method in the {3} file or configuration file"
+//@@Put0
+ "WWoj:"+ //args: {1}-{3}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="未开始录音,但收到Native PCM数据"
+//@@en="Recording does not start, but Native PCM data is received"
+,"rCAM:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="检测到跨域iframe,NativeRecordReceivePCM无法注入到顶层,已监听postMessage转发兼容传输数据,请自行实现将top层接收到数据转发到本iframe(不限层),不然无法接收到录音数据"
+//@@en="A cross-domain iframe is detected. NativeRecordReceivePCM cannot be injected into the top layer. It has listened to postMessage to be compatible with data transmission. Please implement it by yourself to forward the data received by the top layer to this iframe (no limit on layer), otherwise the recording data cannot be received."
+,"t2OF:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="未开始录音"
+//@@en="Recording not started"
+,"Z2y2:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=app-support/app-native-support.js ***************
+
+
+
+//*************** Begin srcFile=app-support/app.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="重复导入{1}"
+//@@en="Duplicate import {1}"
+//@@Put0
+ "uXtA:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="注意:因为并发调用了其他录音相关方法,当前 {1} 的调用结果已被丢弃且不会有回调"
+//@@en="Note: Because other recording-related methods are called concurrently, the current call result of {1} has been discarded and there will be no callback"
+,"kIBu:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="重复注册{1}"
+//@@en="Duplicate registration {1}"
+,"ha2K:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="仅清理资源"
+//@@en="Clean resources only"
+,"wpTL:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="未开始录音"
+//@@en="Recording not started"
+,"bpvP:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="当前环境不支持实时回调,无法进行{1}"
+//@@en="The current environment does not support real-time callback and cannot be performed {1}"
+,"fLJD:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="录音权限请求失败:"
+//@@en="Recording permission request failed: "
+,"YnzX:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="需先调用{1}"
+//@@en="Need to call {1} first"
+,"nwKR:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="当前不是浏览器环境,需引入针对此平台的支持文件({1}),或调用{2}自行实现接入"
+//@@en="This is not a browser environment. You need to import support files for this platform ({1}), or call {2} to implement the access yourself."
+,"citA:"+ //args: {1}-{2}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="开始录音失败:"
+//@@en="Failed to start recording: "
+,"ecp9:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="不能录音:"
+//@@en="Cannot record: "
+,"EKmS:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="已开始录音"
+//@@en="Recording started"
+,"k7Qo:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="结束录音失败:"
+//@@en="Failed to stop recording: "
+,"Douz:"+ //no args
+       "" /** TODO: translate to your-language **/
+
+//@@zh="和Start时差:{1}ms"
+//@@en="Time difference from Start: {1}ms"
+,"wqSH:"+ //args: {1}
+       "" /** TODO: translate to your-language **/
+
+//@@zh="结束录音 耗时{1}ms 音频时长{2}ms 文件大小{3}b {4}"
+//@@en="Stop recording, takes {1}ms, audio duration {2}ms, file size {3}b, {4}"
+,"g3VX:"+ //args: {1}-{4}
+       "" /** TODO: translate to your-language **/
+
+]);
+//*************** End srcFile=app-support/app.js ***************
+
+//@@User Code-2 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-2 End @@
+
+}));

+ 766 - 0
src/i18n/en-US.js

@@ -0,0 +1,766 @@
+/*
+Recorder i18n/en-US.js
+https://github.com/xiangyuecn/Recorder
+
+Usage: Recorder.i18n.lang="en-US" or "en"
+
+Desc: English, 英语。This translation mainly comes from: google translation + Baidu translation, translated from Chinese to English. 此翻译主要来自:google翻译+百度翻译,由中文翻译成英文。
+
+注意:请勿修改//@@打头的文本行;以下代码结构由/src/package-i18n.js自动生成,只允许在字符串中填写翻译后的文本,请勿改变代码结构;翻译的文本如果需要明确的空值,请填写"=Empty";文本中的变量用{n}表示(n代表第几个变量),所有变量必须都出现至少一次,如果不要某变量用{n!}表示
+
+Note: Do not modify the text lines starting with //@@; The following code structure is automatically generated by /src/package-i18n.js, only the translated text is allowed to be filled in the string, please do not change the code structure; If the translated text requires an explicit empty value, please fill in "=Empty"; Variables in the text are represented by {n} (n represents the number of variables), all variables must appear at least once, if a variable is not required, it is represented by {n!}
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	factory(win.Recorder,browser);
+}(function(Recorder,isBrowser){
+"use strict";
+var i18n=Recorder.i18n;
+
+//@@User Code-1 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-1 End @@
+
+//@@Exec i18n.lang="en-US";
+Recorder.CLog('Import Recorder i18n lang="en-US"');
+
+i18n.alias["en-US"]="en";
+
+var putSet={lang:"en"};
+
+i18n.data["rtl$en"]=false;
+i18n.data["desc$en"]="English, 英语。This translation mainly comes from: google translation + Baidu translation, translated from Chinese to English. 此翻译主要来自:google翻译+百度翻译,由中文翻译成英文。";
+//@@Exec i18n.GenerateDisplayEnglish=false;
+
+
+
+//*************** Begin srcFile=recorder-core.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="重复导入{1}"
+//@@Put0
+ "K8zP:"+ //args: {1}
+       "Duplicate import {1}"
+
+//@@zh="剩{1}个GetContext未close"
+,"mSxV:"+ //args: {1}
+       "There are {1} GetContext unclosed"
+
+//@@zh="(注意:ctx不是running状态,rec.open和start至少要有一个在用户操作(触摸、点击等)时进行调用,否则将在rec.start时尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)"
+,"nMIy:"+ //no args
+       " (Note: ctx is not in the running state. At least one of rec.open and start must be called during user operations (touch, click, etc.), otherwise ctx.resume will be attempted during rec.start, which may cause compatibility issues (iOS only), please refer to the runningContext configuration in the documentation) "
+
+//@@zh="。由于{1}内部1秒375次回调,在移动端可能会有性能问题导致回调丢失录音变短,PC端无影响,暂不建议开启{1}。"
+,"ZGlf:"+ //args: {1}
+       ". Due to 375 callbacks in 1 second in {1}, there may be performance problems on the mobile side, which may cause the callback to be lost and the recording to be shortened, but it will not affect the PC side. It is not recommended to enable {1} for now."
+
+//@@zh="Connect采用老的{1},"
+,"7TU0:"+ //args: {1}
+       "Connect uses the old {1}, "
+
+//@@zh="但已设置{1}尝试启用{2}"
+,"JwCL:"+ //args: {1}-{2}
+       "But {1} is set trying to enable {2}"
+
+//@@zh="可设置{1}尝试启用{2}"
+,"VGjB:"+ //args: {1}-{2}
+       "Can set {1} try to enable {2}"
+
+//@@zh="{1}未返回任何音频,恢复使用{2}"
+,"MxX1:"+ //args: {1}-{2}
+       "{1} did not return any audio, reverting to {2}"
+
+//@@zh="{1}多余回调"
+,"XUap:"+ //args: {1}
+       "{1} redundant callback"
+
+//@@zh="Connect采用{1},设置{2}可恢复老式{3}"
+,"yOta:"+ //args: {1}-{3}
+       "Connect uses {1}, set {2} to restore old-fashioned {3}"
+
+//@@zh="(此浏览器不支持{1})"
+,"VwPd:"+ //args: {1}
+       " (This browser does not support {1}) "
+
+//@@zh="{1}未返回任何音频,降级使用{2}"
+,"vHnb:"+ //args: {1}-{2}
+       "{1} did not return any audio, downgrade to {2}"
+
+//@@zh="{1}多余回调"
+,"O9P7:"+ //args: {1}
+       "{1} redundant callback"
+
+//@@zh="Connect采用{1},设置{2}可恢复使用{3}或老式{4}"
+,"LMEm:"+ //args: {1}-{4}
+       "Connect uses {1}, set {2} to restore to using {3} or old-fashioned {4}"
+
+//@@zh="{1}的filter采样率变了,重设滤波"
+,"d48C:"+ //args: {1}
+       "The filter sampleRate of {1} has changed, reset the filter"
+
+//@@zh="{1}似乎传入了未重置chunk {2}"
+,"tlbC:"+ //args: {1}-{2}
+       "{1} seems to have passed in an unreset chunk {2}"
+
+//@@zh="{1}和{2}必须是数值"
+,"VtS4:"+ //args: {1}-{2}
+       "{1} and {2} must be number"
+
+//@@zh="录音open失败:"
+,"5tWi:"+ //no args
+       "Recording open failed: "
+
+//@@zh="open被取消"
+,"dFm8:"+ //no args
+       "open cancelled"
+
+//@@zh="open被中断"
+,"VtJO:"+ //no args
+       "open interrupted"
+
+//@@zh=",可尝试使用RecordApp解决方案"
+,"EMJq:"+ //no args
+       ", you can try to use the RecordApp solution "
+
+//@@zh="不能录音:"
+,"A5bm:"+ //no args
+       "Cannot record: "
+
+//@@zh="不支持此浏览器从流中获取录音"
+,"1iU7:"+ //no args
+       "This browser does not support obtaining recordings from stream"
+
+//@@zh="从流中打开录音失败:"
+,"BTW2:"+ //no args
+       "Failed to open recording from stream: "
+
+//@@zh="无权录音(跨域,请尝试给iframe添加麦克风访问策略,如{1})"
+,"Nclz:"+ //args: {1}
+       "No permission to record (cross domain, please try adding microphone access policy to iframe, such as: {1})"
+
+//@@zh="用户拒绝了录音权限"
+,"gyO5:"+ //no args
+       "User denied recording permission"
+
+//@@zh="浏览器禁止不安全页面录音,可开启https解决"
+,"oWNo:"+ //no args
+       "Browser prohibits recording of unsafe pages, which can be resolved by enabling HTTPS"
+
+//@@zh=",无可用麦克风"
+,"jBa9:"+ //no args
+       ", no microphone available"
+
+//@@zh="此浏览器不支持录音"
+,"COxc:"+ //no args
+       "This browser does not support recording"
+
+//@@zh="发现同时多次调用open"
+,"upb8:"+ //no args
+       "It was found that open was called multiple times at the same time"
+
+//@@zh="录音功能无效:无音频流"
+,"Q1GA:"+ //no args
+       "Invalid recording: no audio stream"
+
+//@@zh="请求录音权限错误"
+,"xEQR:"+ //no args
+       "Error requesting recording permission"
+
+//@@zh="无法录音:"
+,"bDOG:"+ //no args
+       "Unable to record: "
+
+//@@zh=",未配置noiseSuppression和echoCancellation时浏览器可能会自动打开降噪和回声消除,移动端可能会降低系统播放音量(关闭录音后可恢复),请参阅文档中audioTrackSet配置"
+,"RiWe:"+ //no args
+       ", when noiseSuppression and echoCancellation are not configured, the browser may automatically enable noise suppression and echo cancellation, and the mobile terminal may reduce the system playback volume (recovery after the recording is closed), please refer to the audioTrackSet configuration in the document."
+
+//@@zh="close被忽略(因为同时open了多个rec,只有最后一个会真正close)"
+,"hWVz:"+ //no args
+       "close is ignored (because multiple recs are open at the same time, only the last one will actually close)"
+
+//@@zh="忽略"
+,"UHvm:"+ //no args
+       "ignore"
+
+//@@zh="不支持{1}架构"
+,"Essp:"+ //args: {1}
+       "{1} architecture not supported"
+
+//@@zh="{1}类型不支持设置takeoffEncodeChunk"
+,"2XBl:"+ //args: {1}
+       "{1} type does not support setting takeoffEncodeChunk"
+
+//@@zh="(未加载编码器)"
+,"LG7e:"+ //no args
+       "(Encoder not loaded)"
+
+//@@zh="{1}环境不支持实时处理"
+,"7uMV:"+ //args: {1}
+       "{1} environment does not support real-time processing"
+
+//@@zh="补偿{1}ms"
+,"4Kfd:"+ //args: {1}
+       "Compensation {1}ms"
+
+//@@zh="未补偿{1}ms"
+,"bM5i:"+ //args: {1}
+       "Uncompensated {1}ms"
+
+//@@zh="回调出错是不允许的,需保证不会抛异常"
+,"gFUF:"+ //no args
+       "Callback error is not allowed, you need to ensure that no exception will be thrown"
+
+//@@zh="低性能,耗时{1}ms"
+,"2ghS:"+ //args: {1}
+       "Low performance, took {1}ms"
+
+//@@zh="未进入异步前不能清除buffers"
+,"ufqH:"+ //no args
+       "Buffers cannot be cleared before entering async"
+
+//@@zh="start失败:未open"
+,"6WmN:"+ //no args
+       "start failed: not open"
+
+//@@zh="start 开始录音,"
+,"kLDN:"+ //no args
+       "start recording, "
+
+//@@zh="start被中断"
+,"Bp2y:"+ //no args
+       "start was interrupted"
+
+//@@zh=",可能无法录音:"
+,"upkE:"+ //no args
+       ", may fail to record: "
+
+//@@zh="stop 和start时差:"
+,"Xq4s:"+ //no args
+       "Stop and start time difference: "
+
+//@@zh="补偿:"
+,"3CQP:"+ //no args
+       "compensate: "
+
+//@@zh="结束录音失败:"
+,"u8JG:"+ //no args
+       "Failed to stop recording: "
+
+//@@zh=",请设置{1}"
+,"1skY:"+ //args: {1}
+       ", please set {1}"
+
+//@@zh="结束录音 编码花{1}ms 音频时长{2}ms 文件大小{3}b"
+,"Wv7l:"+ //args: {1}-{3}
+       "Stop recording, encoding takes {1}ms, audio duration {2}ms, file size {3}b"
+
+//@@zh="{1}编码器返回的不是{2}"
+,"Vkbd:"+ //args: {1}-{2}
+       "{1} encoder returned not {2}"
+
+//@@zh="启用takeoffEncodeChunk后stop返回的blob长度为0不提供音频数据"
+,"QWnr:"+ //no args
+       "After enabling takeoffEncodeChunk, the length of the blob returned by stop is 0 and no audio data is provided"
+
+//@@zh="生成的{1}无效"
+,"Sz2H:"+ //args: {1}
+       "Invalid generated {1}"
+
+//@@zh="未开始录音"
+,"wf9t:"+ //no args
+       "Recording not started"
+
+//@@zh=",开始录音前无用户交互导致AudioContext未运行"
+,"Dl2c:"+ //no args
+       ", No user interaction before starting recording, resulting in AudioContext not running"
+
+//@@zh="未采集到录音"
+,"Ltz3:"+ //no args
+       "Recording not captured"
+
+//@@zh="未加载{1}编码器,请尝试到{2}的src/engine内找到{1}的编码器并加载"
+,"xGuI:"+ //args: {1}-{2}
+       "The {1} encoder is not loaded. Please try to find the {1} encoder in the src/engine directory of the {2} and load it"
+
+//@@zh="录音错误:"
+,"AxOH:"+ //no args
+       "Recording error: "
+
+//@@zh="音频buffers被释放"
+,"xkKd:"+ //no args
+       "Audio buffers are released"
+
+//@@zh="采样:{1} 花:{2}ms"
+,"CxeT:"+ //args: {1}-{2}
+       "Sampled: {1}, took: {2}ms"
+
+//@@zh="非浏览器环境,不支持{1}"
+,"NonBrowser-1:"+ //args: {1}
+       "Non-browser environment, does not support {1}"
+
+//@@zh="参数错误:{1}"
+,"IllegalArgs-1:"+ //args: {1}
+       "Illegal argument: {1}"
+
+//@@zh="调用{1}需要先导入{2}"
+,"NeedImport-2:"+ //args: {1}-{2}
+       "Calling {1} needs to import {2} first"
+
+//@@zh="不支持:{1}"
+,"NotSupport-1:"+ //args: {1}
+       "Not support: {1}"
+
+//@@zh="覆盖导入{1}"
+,"8HO5:"+ //args: {1}
+       "Override import {1}"
+
+]);
+//*************** End srcFile=recorder-core.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-amr.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="AMR-NB(NarrowBand),采样率设置无效(只提供8000hz),比特率范围:{1}(默认12.2kbps),一帧20ms、{2}字节;浏览器一般不支持播放amr格式,可用Recorder.amr2wav()转码成wav播放"
+//@@Put0
+ "b2mN:"+ //args: {1}-{2}
+       "AMR-NB (NarrowBand), sampleRate setting is invalid (only 8000hz is provided), bitRate range: {1} (default 12.2kbps), one frame 20ms, {2} bytes; browsers generally do not support playing amr format, available Recorder.amr2wav() transcoding into wav playback"
+
+//@@zh="AMR Info: 和设置的不匹配{1},已更新成{2}"
+,"tQBv:"+ //args: {1}-{2}
+       "AMR Info: does not match the set {1}, has been updated to {2}"
+
+//@@zh="数据采样率低于{1}"
+,"q12D:"+ //args: {1}
+       "Data sampleRate lower than {1}"
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+,"TxjV:"+ //no args
+       "The current browser version is too low to process in real time"
+
+//@@zh="takeoffEncodeChunk接管AMR编码器输出的二进制数据,只有首次回调数据(首帧)包含AMR头;在合并成AMR文件时,如果没有把首帧数据包含进去,则必须在文件开头添加上AMR头:Recorder.AMR.AMR_HEADER(转成二进制),否则无法播放"
+,"Q7p7:"+ //no args
+       "takeoffEncodeChunk takes over the binary data output by the AMR encoder, and only the first callback data (the first frame) contains the AMR header; when merging into an AMR file, if the first frame data is not included, the AMR header must be added at the beginning of the file: Recorder.AMR.AMR_HEADER (converted to binary), otherwise it cannot be played"
+
+//@@zh="当前环境不支持Web Worker,amr实时编码器运行在主线程中"
+,"6o9Z:"+ //no args
+       "The current environment does not support Web Worker, and the amr real-time encoder runs in the main thread"
+
+//@@zh="amr worker剩{1}个未stop"
+,"yYWs:"+ //args: {1}
+       "amr worker left {1} unstopped"
+
+//@@zh="amr编码器未start"
+,"jOi8:"+ //no args
+       "amr encoder not started"
+
+]);
+//*************** End srcFile=engine/beta-amr.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-ogg.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="Ogg Vorbis,比特率取值16-100kbps,采样率取值无限制"
+//@@Put0
+ "O8Gn:"+ //no args
+       "Ogg Vorbis, bitRate 16-100kbps, sampleRate unlimited"
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+,"5si6:"+ //no args
+       "The current browser version is too low to process in real time"
+
+//@@zh="takeoffEncodeChunk接管OggVorbis编码器输出的二进制数据,Ogg由数据页组成,一页包含多帧音频数据(含几秒的音频,一页数据无法单独解码和播放),此编码器每次输出都是完整的一页数据,因此实时性会比较低;在合并成完整ogg文件时,必须将输出的所有数据合并到一起,否则可能无法播放,不支持截取中间一部分单独解码和播放"
+,"R8yz:"+ //no args
+       "takeoffEncodeChunk takes over the binary data output by the OggVorbis encoder. Ogg is composed of data pages. One page contains multiple frames of audio data (including a few seconds of audio, and one page of data cannot be decoded and played alone). This encoder outputs a complete page of data each time, so the real-time performance will be relatively low; when merging into a complete ogg file, all the output data must be merged together, otherwise it may not be able to play, and it does not support intercepting the middle part to decode and play separately"
+
+//@@zh="当前环境不支持Web Worker,OggVorbis实时编码器运行在主线程中"
+,"hB9D:"+ //no args
+       "The current environment does not support Web Worker, and the OggVorbis real-time encoder runs in the main thread"
+
+//@@zh="ogg worker剩{1}个未stop"
+,"oTiy:"+ //args: {1}
+       "There are {1} unstopped ogg workers"
+
+//@@zh="ogg编码器未start"
+,"dIpw:"+ //no args
+       "ogg encoder not started"
+
+]);
+//*************** End srcFile=engine/beta-ogg.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-webm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="此浏览器不支持进行webm编码,未实现MediaRecorder"
+//@@Put0
+ "L49q:"+ //no args
+       "This browser does not support webm encoding, MediaRecorder is not implemented"
+
+//@@zh="只有比较新的浏览器支持,压缩率和mp3差不多。由于未找到对已有pcm数据进行快速编码的方法,只能按照类似边播放边收听形式把数据导入到MediaRecorder,有几秒就要等几秒。输出音频虽然可以通过比特率来控制文件大小,但音频文件中的比特率并非设定比特率,采样率由于是我们自己采样的,到这个编码器随他怎么搞"
+,"tsTW:"+ //no args
+       "Only newer browsers support it, and the compression rate is similar to mp3. Since there is no way to quickly encode the existing pcm data, the data can only be imported into MediaRecorder in a similar manner while playing and listening, and it takes a few seconds to wait for a few seconds. Although the output audio can control the file size through the bitRate, the bitRate in the audio file is not the set bitRate. Since the sampleRate is sampled by ourselves, we can do whatever we want with this encoder."
+
+//@@zh="此浏览器不支持把录音转成webm格式"
+,"aG4z:"+ //no args
+       "This browser does not support converting recordings to webm format"
+
+//@@zh="转码webm出错:{1}"
+,"PIX0:"+ //args: {1}
+       "Error encoding webm: {1}"
+
+]);
+//*************** End srcFile=engine/beta-webm.js ***************
+
+
+
+//*************** Begin srcFile=engine/g711x.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="{1};{2}音频文件无法直接播放,可用Recorder.{2}2wav()转码成wav播放;采样率比特率设置无效,固定为8000hz采样率、16位,每个采样压缩成8位存储,音频文件大小为8000字节/秒;如需任意采样率支持,请使用Recorder.{2}_encode()方法"
+//@@Put0
+ "d8YX:"+ //args: {1}-{2}
+       "{1}; {2} audio files cannot be played directly, and can be transcoded into wav by Recorder.{2}2wav(); the sampleRate bitRate setting is invalid, fixed at 8000hz sampleRate, 16 bits, each sample is compressed into 8 bits for storage, and the audio file size is 8000 bytes/second; if you need any sampleRate support, please use Recorder.{2}_encode() Method"
+
+//@@zh="数据采样率低于{1}"
+,"29UK:"+ //args: {1}
+       "Data sampleRate lower than {1}"
+
+//@@zh="{1}编码器未start"
+,"quVJ:"+ //args: {1}
+       "{1} encoder not started"
+
+]);
+//*************** End srcFile=engine/g711x.js ***************
+
+
+
+//*************** Begin srcFile=engine/mp3.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="采样率范围:{1};比特率范围:{2}(不同比特率支持的采样率范围不同,小于32kbps时采样率需小于32000)"
+//@@Put0
+ "Zm7L:"+ //args: {1}-{2}
+       "sampleRate range: {1}; bitRate range: {2} (the sampleRate range supported by different bitRate is different, when the bitRate is less than 32kbps, the sampleRate must be less than 32000)"
+
+//@@zh="{1}不在mp3支持的取值范围:{2}"
+,"eGB9:"+ //args: {1}-{2}
+       "{1} is not in the value range supported by mp3: {2}"
+
+//@@zh="sampleRate已更新为{1},因为{2}不在mp3支持的取值范围:{3}"
+,"zLTa:"+ //args: {1}-{3}
+       "sampleRate has been updated to {1}, because {2} is not in the value range supported by mp3: {3}"
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+,"yhUs:"+ //no args
+       "The current browser version is too low to process in real time"
+
+//@@zh="当前环境不支持Web Worker,mp3实时编码器运行在主线程中"
+,"k9PT:"+ //no args
+       "The current environment does not support Web Worker, and the mp3 real-time encoder runs in the main thread"
+
+//@@zh="mp3 worker剩{1}个未stop"
+,"fT6M:"+ //args: {1}
+       "There are {1} unstopped mp3 workers left"
+
+//@@zh="mp3编码器未start"
+,"mPxH:"+ //no args
+       "mp3 encoder not started"
+
+//@@zh="和设置的不匹配{1},已更新成{2}"
+,"uY9i:"+ //args: {1}-{2}
+       "Does not match the set {1}, has been updated to {2}"
+
+//@@zh="Fix移除{1}帧"
+,"iMSm:"+ //args: {1}
+       "Fix remove {1} frame"
+
+//@@zh="移除帧数过多"
+,"b9zm:"+ //no args
+       "Remove too many frames"
+
+]);
+//*************** End srcFile=engine/mp3.js ***************
+
+
+
+//*************** Begin srcFile=engine/pcm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="pcm为未封装的原始音频数据,pcm音频文件无法直接播放,可用Recorder.pcm2wav()转码成wav播放;支持位数8位、16位(填在比特率里面),采样率取值无限制"
+//@@Put0
+ "fWsN:"+ //no args
+       "pcm is unencapsulated original audio data, pcm audio files cannot be played directly, and can be transcoded into wav by Recorder.pcm2wav(); it supports 8-bit and 16-bit bits (fill in the bitRate), and the sampleRate is unlimited"
+
+//@@zh="PCM Info: 不支持{1}位,已更新成{2}位"
+,"uMUJ:"+ //args: {1}-{2}
+       "PCM Info: {1} bit is not supported, has been updated to {2} bit"
+
+//@@zh="pcm2wav必须提供sampleRate和bitRate"
+,"KmRz:"+ //no args
+       "pcm2wav must provide sampleRate and bitRate"
+
+//@@zh="pcm编码器未start"
+,"sDkA:"+ //no args
+       "pcm encoder not started"
+
+]);
+//*************** End srcFile=engine/pcm.js ***************
+
+
+
+//*************** Begin srcFile=engine/wav.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="支持位数8位、16位(填在比特率里面),采样率取值无限制;此编码器仅在pcm数据前加了一个44字节的wav头,编码出来的16位wav文件去掉开头的44字节即可得到pcm(注:其他wav编码器可能不是44字节)"
+//@@Put0
+ "gPSE:"+ //no args
+       "Supports 8-bit and 16-bit bits (fill in the bitRate), and the sampleRate is unlimited; this encoder only adds a 44-byte wav header before the pcm data, and the encoded 16-bit wav file removes the beginning 44 bytes to get pcm (note: other wav encoders may not be 44 bytes)"
+
+//@@zh="WAV Info: 不支持{1}位,已更新成{2}位"
+,"wyw9:"+ //args: {1}-{2}
+       "WAV Info: {1} bit is not supported, has been updated to {2} bit"
+
+]);
+//*************** End srcFile=engine/wav.js ***************
+
+
+
+//*************** Begin srcFile=extensions/buffer_stream.player.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="getAudioSrc方法已过时:请直接使用getMediaStream然后赋值给audio.srcObject,仅允许在不支持srcObject的浏览器中调用本方法赋值给audio.src以做兼容"
+//@@Put0
+ "0XYC:"+ //no args
+       "The getAudioSrc method is obsolete: please use getMediaStream directly and then assign it to audio.srcObject, it is only allowed to call this method in browsers that do not support srcObject and assign it to audio.src for compatibility"
+
+//@@zh="start被stop终止"
+,"6DDt:"+ //no args
+       "start is terminated by stop"
+
+//@@zh="{1}多次start"
+,"I4h4:"+ //args: {1}
+       "{1} repeat start"
+
+//@@zh="浏览器不支持打开{1}"
+,"P6Gs:"+ //args: {1}
+       "The browser does not support opening {1}"
+
+//@@zh="(注意:ctx不是running状态,start需要在用户操作(触摸、点击等)时进行调用,否则会尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)"
+,"JwDm:"+ //no args
+       " (Note: ctx is not in the running state, start needs to be called when the user operates (touch, click, etc.), otherwise it will try to perform ctx.resume, which may cause compatibility issues (only iOS), please refer to the runningContext configuration in the document) "
+
+//@@zh="此浏览器的AudioBuffer实现不支持动态特性,采用兼容模式"
+,"qx6X:"+ //no args
+       "The AudioBuffer implementation of this browser does not support dynamic features, use compatibility mode"
+
+//@@zh="环境检测超时"
+,"cdOx:"+ //no args
+       "Environment detection timeout"
+
+//@@zh="可能无法播放:{1}"
+,"S2Bu:"+ //args: {1}
+       "Could not play: {1}"
+
+//@@zh="input调用失败:非pcm[Int16,...]输入时,必须解码或者使用transform转换"
+,"ZfGG:"+ //no args
+       "input call failed: non-pcm[Int16,...] input must be decoded or converted using transform"
+
+//@@zh="input调用失败:未提供sampleRate"
+,"N4ke:"+ //no args
+       "input call failed: sampleRate not provided"
+
+//@@zh="input调用失败:data的sampleRate={1}和之前的={2}不同"
+,"IHZd:"+ //args: {1}-{2}
+       "input call failed: sampleRate={1} of data is different from previous={2}"
+
+//@@zh="{1}未调用start方法"
+,"TZPq:"+ //args: {1}
+       "{1} did not call the start method"
+
+//@@zh="浏览器不支持音频解码"
+,"iCFC:"+ //no args
+       "Browser does not support audio decoding"
+
+//@@zh="音频解码数据必须是ArrayBuffer"
+,"wE2k:"+ //no args
+       "Audio decoding data must be ArrayBuffer"
+
+//@@zh="音频解码失败:{1}"
+,"mOaT:"+ //args: {1}
+       "Audio decoding failed: {1}"
+
+]);
+//*************** End srcFile=extensions/buffer_stream.player.js ***************
+
+
+
+//*************** Begin srcFile=extensions/create-audio.nmn2pcm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="符号[{1}]无效:{2}"
+//@@Put0
+ "3RBa:"+ //args: {1}-{2}
+       "Invalid symbol [{1}]: {2}"
+
+//@@zh="音符[{1}]无效:{2}"
+,"U212:"+ //args: {1}-{2}
+       "Invalid note [{1}]: {2}"
+
+//@@zh="多个音时必须对齐,相差{1}ms"
+,"7qAD:"+ //args: {1}
+       "Multiple tones must be aligned, with a difference of {1}ms"
+
+//@@zh="祝你生日快乐"
+,"QGsW:"+ //no args
+       "Happy Birthday to You"
+
+//@@zh="致爱丽丝"
+,"emJR:"+ //no args
+       "For Elise"
+
+//@@zh="卡农-右手简谱"
+,"GsYy:"+ //no args
+       "Canon - Right Hand Notation"
+
+//@@zh="卡农"
+,"bSFZ:"+ //no args
+       "Canon"
+
+]);
+//*************** End srcFile=extensions/create-audio.nmn2pcm.js ***************
+
+
+
+//*************** Begin srcFile=extensions/sonic.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="当前环境不支持Web Worker,不支持调用Sonic.Async"
+//@@Put0
+ "Ikdz:"+ //no args
+       "The current environment does not support Web Worker and does not support calling Sonic.Async"
+
+//@@zh="sonic worker剩{1}个未flush"
+,"IC5Y:"+ //args: {1}
+       "There are {1} unflushed sonic workers left"
+
+]);
+//*************** End srcFile=extensions/sonic.js ***************
+
+
+
+//*************** Begin srcFile=app-support/app-native-support.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="{1}中的{2}方法未实现,请在{3}文件中或配置文件中实现此方法"
+//@@Put0
+ "WWoj:"+ //args: {1}-{3}
+       "The {2} method in {1} is not implemented, please implement this method in the {3} file or configuration file"
+
+//@@zh="未开始录音,但收到Native PCM数据"
+,"rCAM:"+ //no args
+       "Recording does not start, but Native PCM data is received"
+
+//@@zh="检测到跨域iframe,NativeRecordReceivePCM无法注入到顶层,已监听postMessage转发兼容传输数据,请自行实现将top层接收到数据转发到本iframe(不限层),不然无法接收到录音数据"
+,"t2OF:"+ //no args
+       "A cross-domain iframe is detected. NativeRecordReceivePCM cannot be injected into the top layer. It has listened to postMessage to be compatible with data transmission. Please implement it by yourself to forward the data received by the top layer to this iframe (no limit on layer), otherwise the recording data cannot be received."
+
+//@@zh="未开始录音"
+,"Z2y2:"+ //no args
+       "Recording not started"
+
+]);
+//*************** End srcFile=app-support/app-native-support.js ***************
+
+
+
+//*************** Begin srcFile=app-support/app.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="重复导入{1}"
+//@@Put0
+ "uXtA:"+ //args: {1}
+       "Duplicate import {1}"
+
+//@@zh="注意:因为并发调用了其他录音相关方法,当前 {1} 的调用结果已被丢弃且不会有回调"
+,"kIBu:"+ //args: {1}
+       "Note: Because other recording-related methods are called concurrently, the current call result of {1} has been discarded and there will be no callback"
+
+//@@zh="重复注册{1}"
+,"ha2K:"+ //args: {1}
+       "Duplicate registration {1}"
+
+//@@zh="仅清理资源"
+,"wpTL:"+ //no args
+       "Clean resources only"
+
+//@@zh="未开始录音"
+,"bpvP:"+ //no args
+       "Recording not started"
+
+//@@zh="当前环境不支持实时回调,无法进行{1}"
+,"fLJD:"+ //args: {1}
+       "The current environment does not support real-time callback and cannot be performed {1}"
+
+//@@zh="录音权限请求失败:"
+,"YnzX:"+ //no args
+       "Recording permission request failed: "
+
+//@@zh="需先调用{1}"
+,"nwKR:"+ //args: {1}
+       "Need to call {1} first"
+
+//@@zh="当前不是浏览器环境,需引入针对此平台的支持文件({1}),或调用{2}自行实现接入"
+,"citA:"+ //args: {1}-{2}
+       "This is not a browser environment. You need to import support files for this platform ({1}), or call {2} to implement the access yourself."
+
+//@@zh="开始录音失败:"
+,"ecp9:"+ //no args
+       "Failed to start recording: "
+
+//@@zh="不能录音:"
+,"EKmS:"+ //no args
+       "Cannot record: "
+
+//@@zh="已开始录音"
+,"k7Qo:"+ //no args
+       "Recording started"
+
+//@@zh="结束录音失败:"
+,"Douz:"+ //no args
+       "Failed to stop recording: "
+
+//@@zh="和Start时差:{1}ms"
+,"wqSH:"+ //args: {1}
+       "Time difference from Start: {1}ms"
+
+//@@zh="结束录音 耗时{1}ms 音频时长{2}ms 文件大小{3}b {4}"
+,"g3VX:"+ //args: {1}-{4}
+       "Stop recording, takes {1}ms, audio duration {2}ms, file size {3}b, {4}"
+
+]);
+//*************** End srcFile=app-support/app.js ***************
+
+//@@User Code-2 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-2 End @@
+
+}));

+ 915 - 0
src/i18n/es.js

@@ -0,0 +1,915 @@
+/*
+Recorder i18n/es.js
+https://github.com/xiangyuecn/Recorder
+
+Usage: Recorder.i18n.lang="es"
+
+Desc: Spanish, Español, 西班牙语。Esta traducción proviene principalmente de: traducción de google + traducción de Baidu, traducida del chino al español. 此翻译主要来自:google翻译+百度翻译,由中文翻译成西班牙语。
+
+注意:请勿修改//@@打头的文本行;以下代码结构由/src/package-i18n.js自动生成,只允许在字符串中填写翻译后的文本,请勿改变代码结构;翻译的文本如果需要明确的空值,请填写"=Empty";文本中的变量用{n}表示(n代表第几个变量),所有变量必须都出现至少一次,如果不要某变量用{n!}表示
+
+Note: Do not modify the text lines starting with //@@; The following code structure is automatically generated by /src/package-i18n.js, only the translated text is allowed to be filled in the string, please do not change the code structure; If the translated text requires an explicit empty value, please fill in "=Empty"; Variables in the text are represented by {n} (n represents the number of variables), all variables must appear at least once, if a variable is not required, it is represented by {n!}
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	factory(win.Recorder,browser);
+}(function(Recorder,isBrowser){
+"use strict";
+var i18n=Recorder.i18n;
+
+//@@User Code-1 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-1 End @@
+
+//@@Exec i18n.lang="es";
+Recorder.CLog('Import Recorder i18n lang="es"');
+
+//i18n.alias["other-lang-key"]="es";
+
+var putSet={lang:"es"};
+
+i18n.data["rtl$es"]=false;
+i18n.data["desc$es"]="Spanish, Español, 西班牙语。Esta traducción proviene principalmente de: traducción de google + traducción de Baidu, traducida del chino al español. 此翻译主要来自:google翻译+百度翻译,由中文翻译成西班牙语。";
+//@@Exec i18n.GenerateDisplayEnglish=true;
+
+
+
+//*************** Begin srcFile=recorder-core.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="重复导入{1}"
+//@@en="Duplicate import {1}"
+//@@Put0
+ "K8zP:"+ //args: {1}
+       "Importación duplicada {1}"
+
+//@@zh="剩{1}个GetContext未close"
+//@@en="There are {1} GetContext unclosed"
+,"mSxV:"+ //args: {1}
+       "Los {1} GetContext restantes no han sido close"
+
+//@@zh="(注意:ctx不是running状态,rec.open和start至少要有一个在用户操作(触摸、点击等)时进行调用,否则将在rec.start时尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)"
+//@@en=" (Note: ctx is not in the running state. At least one of rec.open and start must be called during user operations (touch, click, etc.), otherwise ctx.resume will be attempted during rec.start, which may cause compatibility issues (iOS only), please refer to the runningContext configuration in the documentation) "
+,"nMIy:"+ //no args
+       " (Nota: ctx no está en estado running. Se debe llamar al menos a uno de rec.open y start durante la operación del usuario (tocar, hacer clic, etc.); de lo contrario, se intentará ctx.resume durante rec.start, lo que puede causar compatibilidad problemas (solo iOS), consulte la configuración de runningContext en la documentación) "
+
+//@@zh="。由于{1}内部1秒375次回调,在移动端可能会有性能问题导致回调丢失录音变短,PC端无影响,暂不建议开启{1}。"
+//@@en=". Due to 375 callbacks in 1 second in {1}, there may be performance problems on the mobile side, which may cause the callback to be lost and the recording to be shortened, but it will not affect the PC side. It is not recommended to enable {1} for now."
+,"ZGlf:"+ //args: {1}
+       ". Debido a las 375 devoluciones de llamadas por segundo dentro de {1}, puede haber problemas de rendimiento en el lado móvil que pueden provocar que se pierdan las devoluciones de llamadas y que la grabación sea más corta. No hay ningún impacto en el lado de la PC. No se recomienda habilitar {1} por el momento."
+
+//@@zh="Connect采用老的{1},"
+//@@en="Connect uses the old {1}, "
+,"7TU0:"+ //args: {1}
+       "Connect utiliza el antiguo {1}, "
+
+//@@zh="但已设置{1}尝试启用{2}"
+//@@en="But {1} is set trying to enable {2}"
+,"JwCL:"+ //args: {1}-{2}
+       "Pero {1} está configurado para intentar habilitar {2}"
+
+//@@zh="可设置{1}尝试启用{2}"
+//@@en="Can set {1} try to enable {2}"
+,"VGjB:"+ //args: {1}-{2}
+       "Puedes configurar {1} para intentar habilitar {2}"
+
+//@@zh="{1}未返回任何音频,恢复使用{2}"
+//@@en="{1} did not return any audio, reverting to {2}"
+,"MxX1:"+ //args: {1}-{2}
+       "{1} no devolvió ningún audio, continúe usando {2}"
+
+//@@zh="{1}多余回调"
+//@@en="{1} redundant callback"
+,"XUap:"+ //args: {1}
+       "{1} devolución de llamada redundante"
+
+//@@zh="Connect采用{1},设置{2}可恢复老式{3}"
+//@@en="Connect uses {1}, set {2} to restore old-fashioned {3}"
+,"yOta:"+ //args: {1}-{3}
+       "Connect usa {1}, configurar {2} puede restaurar el antiguo {3}"
+
+//@@zh="(此浏览器不支持{1})"
+//@@en=" (This browser does not support {1}) "
+,"VwPd:"+ //args: {1}
+       " (Este navegador no soporta {1}) "
+
+//@@zh="{1}未返回任何音频,降级使用{2}"
+//@@en="{1} did not return any audio, downgrade to {2}"
+,"vHnb:"+ //args: {1}-{2}
+       "{1} no devuelve audio, se ha degradado para usar {2}"
+
+//@@zh="{1}多余回调"
+//@@en="{1} redundant callback"
+,"O9P7:"+ //args: {1}
+       "{1} devolución de llamada redundante"
+
+//@@zh="Connect采用{1},设置{2}可恢复使用{3}或老式{4}"
+//@@en="Connect uses {1}, set {2} to restore to using {3} or old-fashioned {4}"
+,"LMEm:"+ //args: {1}-{4}
+       "Connect usa {1}, configure {2} para volver a {3} o al antiguo {4}"
+
+//@@zh="{1}的filter采样率变了,重设滤波"
+//@@en="The filter sampleRate of {1} has changed, reset the filter"
+,"d48C:"+ //args: {1}
+       "La frecuencia de muestreo del filtro de {1} ha cambiado y el filtro se ha restablecido"
+
+//@@zh="{1}似乎传入了未重置chunk {2}"
+//@@en="{1} seems to have passed in an unreset chunk {2}"
+,"tlbC:"+ //args: {1}-{2}
+       "{1} parece haber introducido chunk {2} sin restablecer"
+
+//@@zh="{1}和{2}必须是数值"
+//@@en="{1} and {2} must be number"
+,"VtS4:"+ //args: {1}-{2}
+       "{1} y {2} deben ser valores numéricos"
+
+//@@zh="录音open失败:"
+//@@en="Recording open failed: "
+,"5tWi:"+ //no args
+       "Error al grabar open: "
+
+//@@zh="open被取消"
+//@@en="open cancelled"
+,"dFm8:"+ //no args
+       "open fue cancelado"
+
+//@@zh="open被中断"
+//@@en="open interrupted"
+,"VtJO:"+ //no args
+       "open fue interrumpido"
+
+//@@zh=",可尝试使用RecordApp解决方案"
+//@@en=", you can try to use the RecordApp solution "
+,"EMJq:"+ //no args
+       ", puedes probar la solución RecordApp"
+
+//@@zh="不能录音:"
+//@@en="Cannot record: "
+,"A5bm:"+ //no args
+       "No se puede grabar: "
+
+//@@zh="不支持此浏览器从流中获取录音"
+//@@en="This browser does not support obtaining recordings from stream"
+,"1iU7:"+ //no args
+       "Este navegador no admite la recuperación de grabaciones de transmisiones"
+
+//@@zh="从流中打开录音失败:"
+//@@en="Failed to open recording from stream: "
+,"BTW2:"+ //no args
+       "No se pudo abrir la grabación desde la transmisión: "
+
+//@@zh="无权录音(跨域,请尝试给iframe添加麦克风访问策略,如{1})"
+//@@en="No permission to record (cross domain, please try adding microphone access policy to iframe, such as: {1})"
+,"Nclz:"+ //args: {1}
+       "Sin permiso para grabar (entre dominios, intente agregar una política de acceso al micrófono al iframe, como {1})"
+
+//@@zh="用户拒绝了录音权限"
+//@@en="User denied recording permission"
+,"gyO5:"+ //no args
+       "Usuario denegado permiso de grabación"
+
+//@@zh="浏览器禁止不安全页面录音,可开启https解决"
+//@@en="Browser prohibits recording of unsafe pages, which can be resolved by enabling HTTPS"
+,"oWNo:"+ //no args
+       "El navegador prohíbe el registro de páginas no seguras, lo que se puede solucionar activando https"
+
+//@@zh=",无可用麦克风"
+//@@en=", no microphone available"
+,"jBa9:"+ //no args
+       ", no hay micrófono disponible"
+
+//@@zh="此浏览器不支持录音"
+//@@en="This browser does not support recording"
+,"COxc:"+ //no args
+       "Este navegador no admite la grabación"
+
+//@@zh="发现同时多次调用open"
+//@@en="It was found that open was called multiple times at the same time"
+,"upb8:"+ //no args
+       "Descubrí que se llamó a open varias veces al mismo tiempo"
+
+//@@zh="录音功能无效:无音频流"
+//@@en="Invalid recording: no audio stream"
+,"Q1GA:"+ //no args
+       "La función de grabación no funciona: no hay transmisión de audio"
+
+//@@zh="请求录音权限错误"
+//@@en="Error requesting recording permission"
+,"xEQR:"+ //no args
+       "Error al solicitar permiso de grabación"
+
+//@@zh="无法录音:"
+//@@en="Unable to record: "
+,"bDOG:"+ //no args
+       "No se puede grabar: "
+
+//@@zh=",未配置noiseSuppression和echoCancellation时浏览器可能会自动打开降噪和回声消除,移动端可能会降低系统播放音量(关闭录音后可恢复),请参阅文档中audioTrackSet配置"
+//@@en=", when noiseSuppression and echoCancellation are not configured, the browser may automatically enable noise suppression and echo cancellation, and the mobile terminal may reduce the system playback volume (recovery after the recording is closed), please refer to the audioTrackSet configuration in the document."
+,"RiWe:"+ //no args
+       ", cuando no se configuran noiseSuppression y echoCancellation, el navegador puede activar automáticamente la reducción de ruido y la cancelación de eco, y el terminal móvil puede reducir el volumen de reproducción del sistema (recuperable después de apagar la grabación), consulte la configuración de audioTrackSet en el documento"
+
+//@@zh="close被忽略(因为同时open了多个rec,只有最后一个会真正close)"
+//@@en="close is ignored (because multiple recs are open at the same time, only the last one will actually close)"
+,"hWVz:"+ //no args
+       "close se ignora (debido a que se abren varios recs al mismo tiempo, solo el último será realmente close)"
+
+//@@zh="忽略"
+//@@en="ignore"
+,"UHvm:"+ //no args
+       "descuido"
+
+//@@zh="不支持{1}架构"
+//@@en="{1} architecture not supported"
+,"Essp:"+ //args: {1}
+       "No es compatible con la arquitectura {1}"
+
+//@@zh="{1}类型不支持设置takeoffEncodeChunk"
+//@@en="{1} type does not support setting takeoffEncodeChunk"
+,"2XBl:"+ //args: {1}
+       "El tipo {1} no admite la configuración de takeoffEncodeChunk"
+
+//@@zh="(未加载编码器)"
+//@@en="(Encoder not loaded)"
+,"LG7e:"+ //no args
+       "(sin codificador cargado)"
+
+//@@zh="{1}环境不支持实时处理"
+//@@en="{1} environment does not support real-time processing"
+,"7uMV:"+ //args: {1}
+       "El entorno {1} no admite el procesamiento en tiempo real"
+
+//@@zh="补偿{1}ms"
+//@@en="Compensation {1}ms"
+,"4Kfd:"+ //args: {1}
+       "Compensación {1}ms"
+
+//@@zh="未补偿{1}ms"
+//@@en="Uncompensated {1}ms"
+,"bM5i:"+ //args: {1}
+       "{1} ms sin compensar"
+
+//@@zh="回调出错是不允许的,需保证不会抛异常"
+//@@en="Callback error is not allowed, you need to ensure that no exception will be thrown"
+,"gFUF:"+ //no args
+       "No se permiten errores en las devoluciones de llamada y se debe garantizar que no se produzcan excepciones"
+
+//@@zh="低性能,耗时{1}ms"
+//@@en="Low performance, took {1}ms"
+,"2ghS:"+ //args: {1}
+       "Bajo rendimiento, tarda {1} ms"
+
+//@@zh="未进入异步前不能清除buffers"
+//@@en="Buffers cannot be cleared before entering async"
+,"ufqH:"+ //no args
+       "Los buffers no se pueden borrar antes de entrar asíncrono"
+
+//@@zh="start失败:未open"
+//@@en="start failed: not open"
+,"6WmN:"+ //no args
+       "start falló: no open"
+
+//@@zh="start 开始录音,"
+//@@en="start recording, "
+,"kLDN:"+ //no args
+       "start, comenzar a grabar, "
+
+//@@zh="start被中断"
+//@@en="start was interrupted"
+,"Bp2y:"+ //no args
+       "start fue interrumpido"
+
+//@@zh=",可能无法录音:"
+//@@en=", may fail to record: "
+,"upkE:"+ //no args
+       ", es posible que no sea posible grabar: "
+
+//@@zh="stop 和start时差:"
+//@@en="Stop and start time difference: "
+,"Xq4s:"+ //no args
+       "stop, diferencia horaria con start: "
+
+//@@zh="补偿:"
+//@@en="compensate: "
+,"3CQP:"+ //no args
+       "compensar: "
+
+//@@zh="结束录音失败:"
+//@@en="Failed to stop recording: "
+,"u8JG:"+ //no args
+       "No se pudo finalizar la grabación: "
+
+//@@zh=",请设置{1}"
+//@@en=", please set {1}"
+,"1skY:"+ //args: {1}
+       ", por favor establece {1}"
+
+//@@zh="结束录音 编码花{1}ms 音频时长{2}ms 文件大小{3}b"
+//@@en="Stop recording, encoding takes {1}ms, audio duration {2}ms, file size {3}b"
+,"Wv7l:"+ //args: {1}-{3}
+       "Finalizar la grabación. La codificación tarda {1} ms. La duración del audio es de {2} ms. El tamaño del archivo es {3}b"
+
+//@@zh="{1}编码器返回的不是{2}"
+//@@en="{1} encoder returned not {2}"
+,"Vkbd:"+ //args: {1}-{2}
+       "El codificador {1} no devuelve {2}"
+
+//@@zh="启用takeoffEncodeChunk后stop返回的blob长度为0不提供音频数据"
+//@@en="After enabling takeoffEncodeChunk, the length of the blob returned by stop is 0 and no audio data is provided"
+,"QWnr:"+ //no args
+       "Después de habilitar takeoffEncodeChunk, la longitud del blob devuelta por stop es 0 y no se proporcionan datos de audio"
+
+//@@zh="生成的{1}无效"
+//@@en="Invalid generated {1}"
+,"Sz2H:"+ //args: {1}
+       "El {1} generado no es válido"
+
+//@@zh="未开始录音"
+//@@en="Recording not started"
+,"wf9t:"+ //no args
+       "Grabación no iniciada"
+
+//@@zh=",开始录音前无用户交互导致AudioContext未运行"
+//@@en=", No user interaction before starting recording, resulting in AudioContext not running"
+,"Dl2c:"+ //no args
+       ", no hay interacción del usuario antes de comenzar a grabar, lo que hace que AudioContext no se ejecute"
+
+//@@zh="未采集到录音"
+//@@en="Recording not captured"
+,"Ltz3:"+ //no args
+       "No se recopiló ninguna grabación"
+
+//@@zh="未加载{1}编码器,请尝试到{2}的src/engine内找到{1}的编码器并加载"
+//@@en="The {1} encoder is not loaded. Please try to find the {1} encoder in the src/engine directory of the {2} and load it"
+,"xGuI:"+ //args: {1}-{2}
+       "El codificador de {1} no está cargado. Intente encontrar el codificador de {1} en src/engine de {2} y cárguelo"
+
+//@@zh="录音错误:"
+//@@en="Recording error: "
+,"AxOH:"+ //no args
+       "Error de grabación: "
+
+//@@zh="音频buffers被释放"
+//@@en="Audio buffers are released"
+,"xkKd:"+ //no args
+       "Se liberan los buffers de audio"
+
+//@@zh="采样:{1} 花:{2}ms"
+//@@en="Sampled: {1}, took: {2}ms"
+,"CxeT:"+ //args: {1}-{2}
+       "Muestra: {1} Flor: {2}ms"
+
+//@@zh="非浏览器环境,不支持{1}"
+//@@en="Non-browser environment, does not support {1}"
+,"NonBrowser-1:"+ //args: {1}
+       "Entorno sin navegador, no es compatible con {1}"
+
+//@@zh="参数错误:{1}"
+//@@en="Illegal argument: {1}"
+,"IllegalArgs-1:"+ //args: {1}
+       "Error de parámetro: {1}"
+
+//@@zh="调用{1}需要先导入{2}"
+//@@en="Calling {1} needs to import {2} first"
+,"NeedImport-2:"+ //args: {1}-{2}
+       "Para llamar a {1}, primero debes importar {2}"
+
+//@@zh="不支持:{1}"
+//@@en="Not support: {1}"
+,"NotSupport-1:"+ //args: {1}
+       "No compatible: {1}"
+
+//@@zh="覆盖导入{1}"
+//@@en="Override import {1}"
+,"8HO5:"+ //args: {1}
+       "Anular importación {1}"
+
+]);
+//*************** End srcFile=recorder-core.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-amr.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="AMR-NB(NarrowBand),采样率设置无效(只提供8000hz),比特率范围:{1}(默认12.2kbps),一帧20ms、{2}字节;浏览器一般不支持播放amr格式,可用Recorder.amr2wav()转码成wav播放"
+//@@en="AMR-NB (NarrowBand), sampleRate setting is invalid (only 8000hz is provided), bitRate range: {1} (default 12.2kbps), one frame 20ms, {2} bytes; browsers generally do not support playing amr format, available Recorder.amr2wav() transcoding into wav playback"
+//@@Put0
+ "b2mN:"+ //args: {1}-{2}
+       "AMR-NB (NarrowBand), la configuración sampleRate no es válida (solo se proporcionan 8000 hz), rango bitRate: {1} (predeterminado 12.2 kbps), un cuadro de 20 ms, {2} bytes; los navegadores generalmente no admiten la reproducción en formato amr, disponible Recorder.amr2wav() Transcodifica a wav para reproducción"
+
+//@@zh="AMR Info: 和设置的不匹配{1},已更新成{2}"
+//@@en="AMR Info: does not match the set {1}, has been updated to {2}"
+,"tQBv:"+ //args: {1}-{2}
+       "AMR Info: no coincide con el conjunto {1}, se ha actualizado a {2}"
+
+//@@zh="数据采样率低于{1}"
+//@@en="Data sampleRate lower than {1}"
+,"q12D:"+ //args: {1}
+       "Los datos sampleRate están por debajo de {1}"
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+//@@en="The current browser version is too low to process in real time"
+,"TxjV:"+ //no args
+       "La versión actual del navegador es demasiado baja y no se puede procesar en tiempo real"
+
+//@@zh="takeoffEncodeChunk接管AMR编码器输出的二进制数据,只有首次回调数据(首帧)包含AMR头;在合并成AMR文件时,如果没有把首帧数据包含进去,则必须在文件开头添加上AMR头:Recorder.AMR.AMR_HEADER(转成二进制),否则无法播放"
+//@@en="takeoffEncodeChunk takes over the binary data output by the AMR encoder, and only the first callback data (the first frame) contains the AMR header; when merging into an AMR file, if the first frame data is not included, the AMR header must be added at the beginning of the file: Recorder.AMR.AMR_HEADER (converted to binary), otherwise it cannot be played"
+,"Q7p7:"+ //no args
+       "takeoffEncodeChunk se hace cargo de la salida de datos binarios del codificador AMR. Solo los primeros datos de devolución de llamada (primer cuadro) contienen el encabezado AMR; al fusionarlos en un archivo AMR, si los datos del primer cuadro no están incluidos, el encabezado AMR debe agregarse en el comienzo del archivo: Recorder.AMR.AMR_HEADER (convertido a binario), de lo contrario no se puede reproducir"
+
+//@@zh="当前环境不支持Web Worker,amr实时编码器运行在主线程中"
+//@@en="The current environment does not support Web Worker, and the amr real-time encoder runs in the main thread"
+,"6o9Z:"+ //no args
+       "El entorno actual no es compatible con Web Worker y el codificador en tiempo real amr se ejecuta en el hilo principal"
+
+//@@zh="amr worker剩{1}个未stop"
+//@@en="amr worker left {1} unstopped"
+,"yYWs:"+ //args: {1}
+       "a amr worker le quedan {1} no stop"
+
+//@@zh="amr编码器未start"
+//@@en="amr encoder not started"
+,"jOi8:"+ //no args
+       "codificador amr no start"
+
+]);
+//*************** End srcFile=engine/beta-amr.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-ogg.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="Ogg Vorbis,比特率取值16-100kbps,采样率取值无限制"
+//@@en="Ogg Vorbis, bitRate 16-100kbps, sampleRate unlimited"
+//@@Put0
+ "O8Gn:"+ //no args
+       "Ogg Vorbis, el valor de bitRate es de 16-100 kbps, el valor de sampleRate es ilimitado"
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+//@@en="The current browser version is too low to process in real time"
+,"5si6:"+ //no args
+       "La versión actual del navegador es demasiado baja y no se puede procesar en tiempo real"
+
+//@@zh="takeoffEncodeChunk接管OggVorbis编码器输出的二进制数据,Ogg由数据页组成,一页包含多帧音频数据(含几秒的音频,一页数据无法单独解码和播放),此编码器每次输出都是完整的一页数据,因此实时性会比较低;在合并成完整ogg文件时,必须将输出的所有数据合并到一起,否则可能无法播放,不支持截取中间一部分单独解码和播放"
+//@@en="takeoffEncodeChunk takes over the binary data output by the OggVorbis encoder. Ogg is composed of data pages. One page contains multiple frames of audio data (including a few seconds of audio, and one page of data cannot be decoded and played alone). This encoder outputs a complete page of data each time, so the real-time performance will be relatively low; when merging into a complete ogg file, all the output data must be merged together, otherwise it may not be able to play, and it does not support intercepting the middle part to decode and play separately"
+,"R8yz:"+ //no args
+       "takeoffEncodeChunk se hace cargo de la salida de datos binarios del codificador OggVorbis. Ogg se compone de páginas de datos. Una página contiene múltiples fotogramas de datos de audio (incluidos varios segundos de audio. Una página de datos no se puede decodificar ni reproducir por separado). Cada salida de este codificador está completo. Una página de datos, por lo que el rendimiento en tiempo real será relativamente bajo; al fusionar en un archivo ogg completo, todos los datos de salida deben fusionarse; de ​​lo contrario, es posible que no se reproduzca y no se admita intercepte la parte media y decodifíquela y reprodúzcala por separado"
+
+//@@zh="当前环境不支持Web Worker,OggVorbis实时编码器运行在主线程中"
+//@@en="The current environment does not support Web Worker, and the OggVorbis real-time encoder runs in the main thread"
+,"hB9D:"+ //no args
+       "El entorno actual no admite Web Workers y el codificador en tiempo real OggVorbis se ejecuta en el hilo principal"
+
+//@@zh="ogg worker剩{1}个未stop"
+//@@en="There are {1} unstopped ogg workers"
+,"oTiy:"+ //args: {1}
+       "a ogg worker le quedan {1} no stop"
+
+//@@zh="ogg编码器未start"
+//@@en="ogg encoder not started"
+,"dIpw:"+ //no args
+       "codificador ogg no start"
+
+]);
+//*************** End srcFile=engine/beta-ogg.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-webm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="此浏览器不支持进行webm编码,未实现MediaRecorder"
+//@@en="This browser does not support webm encoding, MediaRecorder is not implemented"
+//@@Put0
+ "L49q:"+ //no args
+       "Este navegador no admite la codificación webm y MediaRecorder no está implementado"
+
+//@@zh="只有比较新的浏览器支持,压缩率和mp3差不多。由于未找到对已有pcm数据进行快速编码的方法,只能按照类似边播放边收听形式把数据导入到MediaRecorder,有几秒就要等几秒。输出音频虽然可以通过比特率来控制文件大小,但音频文件中的比特率并非设定比特率,采样率由于是我们自己采样的,到这个编码器随他怎么搞"
+//@@en="Only newer browsers support it, and the compression rate is similar to mp3. Since there is no way to quickly encode the existing pcm data, the data can only be imported into MediaRecorder in a similar manner while playing and listening, and it takes a few seconds to wait for a few seconds. Although the output audio can control the file size through the bitRate, the bitRate in the audio file is not the set bitRate. Since the sampleRate is sampled by ourselves, we can do whatever we want with this encoder."
+,"tsTW:"+ //no args
+       "Sólo los navegadores más nuevos lo admiten y la tasa de compresión es similar a la de mp3. Dado que no hay forma de codificar rápidamente los datos pcm existentes, los datos sólo se pueden importar a MediaRecorder de forma similar a la reproducción y escucha, y hay que esperar unos segundos. Aunque el tamaño del archivo de audio de salida se puede controlar mediante la velocidad de bits, la velocidad de bits en el archivo de audio no es la velocidad de bits establecida. Dado que la frecuencia de muestreo la probamos nosotros mismos, podemos hacer lo que queramos con este codificador"
+
+//@@zh="此浏览器不支持把录音转成webm格式"
+//@@en="This browser does not support converting recordings to webm format"
+,"aG4z:"+ //no args
+       "Este navegador no admite la conversión de grabaciones al formato webm"
+
+//@@zh="转码webm出错:{1}"
+//@@en="Error encoding webm: {1}"
+,"PIX0:"+ //args: {1}
+       "Error al transcodificar webm: {1}"
+
+]);
+//*************** End srcFile=engine/beta-webm.js ***************
+
+
+
+//*************** Begin srcFile=engine/g711x.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="{1};{2}音频文件无法直接播放,可用Recorder.{2}2wav()转码成wav播放;采样率比特率设置无效,固定为8000hz采样率、16位,每个采样压缩成8位存储,音频文件大小为8000字节/秒;如需任意采样率支持,请使用Recorder.{2}_encode()方法"
+//@@en="{1}; {2} audio files cannot be played directly, and can be transcoded into wav by Recorder.{2}2wav(); the sampleRate bitRate setting is invalid, fixed at 8000hz sampleRate, 16 bits, each sample is compressed into 8 bits for storage, and the audio file size is 8000 bytes/second; if you need any sampleRate support, please use Recorder.{2}_encode() Method"
+//@@Put0
+ "d8YX:"+ //args: {1}-{2}
+       "{1}; {2} El archivo de audio no se puede reproducir directamente. Puede utilizar Recorder.{2}2wav() para transcodificarlo a wav para su reproducción; la configuración de velocidad de bits de frecuencia de muestreo no es válida y está fijada en 8000 hz de muestreo velocidad, 16 bits, y cada muestra está comprimida. en un almacenamiento de 8 bits, el tamaño del archivo de audio es 8000 bytes/segundo; si necesita compatibilidad con cualquier frecuencia de muestreo, utilice el método Recorder.{2}_encode()"
+
+//@@zh="数据采样率低于{1}"
+//@@en="Data sampleRate lower than {1}"
+,"29UK:"+ //args: {1}
+       "Los datos sampleRate están por debajo de {1}"
+
+//@@zh="{1}编码器未start"
+//@@en="{1} encoder not started"
+,"quVJ:"+ //args: {1}
+       "codificador {1} no start"
+
+]);
+//*************** End srcFile=engine/g711x.js ***************
+
+
+
+//*************** Begin srcFile=engine/mp3.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="采样率范围:{1};比特率范围:{2}(不同比特率支持的采样率范围不同,小于32kbps时采样率需小于32000)"
+//@@en="sampleRate range: {1}; bitRate range: {2} (the sampleRate range supported by different bitRate is different, when the bitRate is less than 32kbps, the sampleRate must be less than 32000)"
+//@@Put0
+ "Zm7L:"+ //args: {1}-{2}
+       "rango sampleRate: {1}; rango bitRate: {2} (diferentes bitRate admiten diferentes rangos sampleRate. Cuando es inferior a 32 kbps, sampleRate debe ser inferior a 32000)"
+
+//@@zh="{1}不在mp3支持的取值范围:{2}"
+//@@en="{1} is not in the value range supported by mp3: {2}"
+,"eGB9:"+ //args: {1}-{2}
+       "{1} no está en el rango de valores soportado por mp3: {2}"
+
+//@@zh="sampleRate已更新为{1},因为{2}不在mp3支持的取值范围:{3}"
+//@@en="sampleRate has been updated to {1}, because {2} is not in the value range supported by mp3: {3}"
+,"zLTa:"+ //args: {1}-{3}
+       "sampleRate se ha actualizado a {1} porque {2} no está en el rango de valores admitido por mp3: {3}"
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+//@@en="The current browser version is too low to process in real time"
+,"yhUs:"+ //no args
+       "La versión actual del navegador es demasiado baja y no se puede procesar en tiempo real"
+
+//@@zh="当前环境不支持Web Worker,mp3实时编码器运行在主线程中"
+//@@en="The current environment does not support Web Worker, and the mp3 real-time encoder runs in the main thread"
+,"k9PT:"+ //no args
+       "El entorno actual no es compatible con Web Worker y el codificador en tiempo real mp3 se ejecuta en el hilo principal"
+
+//@@zh="mp3 worker剩{1}个未stop"
+//@@en="There are {1} unstopped mp3 workers left"
+,"fT6M:"+ //args: {1}
+       "a mp3 worker le quedan {1} no stop"
+
+//@@zh="mp3编码器未start"
+//@@en="mp3 encoder not started"
+,"mPxH:"+ //no args
+       "codificador mp3 no start"
+
+//@@zh="和设置的不匹配{1},已更新成{2}"
+//@@en="Does not match the set {1}, has been updated to {2}"
+,"uY9i:"+ //args: {1}-{2}
+       "No coincide con la configuración {1}, se ha actualizado a {2}"
+
+//@@zh="Fix移除{1}帧"
+//@@en="Fix remove {1} frame"
+,"iMSm:"+ //args: {1}
+       "Fix elimina {1} fotogramas"
+
+//@@zh="移除帧数过多"
+//@@en="Remove too many frames"
+,"b9zm:"+ //no args
+       "Eliminar demasiados fotogramas"
+
+]);
+//*************** End srcFile=engine/mp3.js ***************
+
+
+
+//*************** Begin srcFile=engine/pcm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="pcm为未封装的原始音频数据,pcm音频文件无法直接播放,可用Recorder.pcm2wav()转码成wav播放;支持位数8位、16位(填在比特率里面),采样率取值无限制"
+//@@en="pcm is unencapsulated original audio data, pcm audio files cannot be played directly, and can be transcoded into wav by Recorder.pcm2wav(); it supports 8-bit and 16-bit bits (fill in the bitRate), and the sampleRate is unlimited"
+//@@Put0
+ "fWsN:"+ //no args
+       "pcm son datos de audio originales no encapsulados. Los archivos de audio Pcm no se pueden reproducir directamente. Recorder.pcm2wav() se puede utilizar para transcodificar a wav para su reproducción. Admite dígitos de 8 y 16 bits (rellene bitRate) y el valor de sampleRate es ilimitado"
+
+//@@zh="PCM Info: 不支持{1}位,已更新成{2}位"
+//@@en="PCM Info: {1} bit is not supported, has been updated to {2} bit"
+,"uMUJ:"+ //args: {1}-{2}
+       "PCM Info: El bit {1} no es compatible y se ha actualizado al bit {2}"
+
+//@@zh="pcm2wav必须提供sampleRate和bitRate"
+//@@en="pcm2wav must provide sampleRate and bitRate"
+,"KmRz:"+ //no args
+       "pcm2wav debe proporcionar sampleRate y bitRate"
+
+//@@zh="pcm编码器未start"
+//@@en="pcm encoder not started"
+,"sDkA:"+ //no args
+       "codificador pcm no start"
+
+]);
+//*************** End srcFile=engine/pcm.js ***************
+
+
+
+//*************** Begin srcFile=engine/wav.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="支持位数8位、16位(填在比特率里面),采样率取值无限制;此编码器仅在pcm数据前加了一个44字节的wav头,编码出来的16位wav文件去掉开头的44字节即可得到pcm(注:其他wav编码器可能不是44字节)"
+//@@en="Supports 8-bit and 16-bit bits (fill in the bitRate), and the sampleRate is unlimited; this encoder only adds a 44-byte wav header before the pcm data, and the encoded 16-bit wav file removes the beginning 44 bytes to get pcm (note: other wav encoders may not be 44 bytes)"
+//@@Put0
+ "gPSE:"+ //no args
+       "Admite dígitos de 8 y 16 bits (completados en bitRate) y el valor de sampleRate es ilimitado; este codificador solo agrega un encabezado wav de 44 bytes antes de los datos pcm, y el archivo wav codificado de 16 bits elimina los 44 bits iniciales. Bytes para obtener pcm (nota: es posible que otros codificadores WAV no tengan 44 bytes)"
+
+//@@zh="WAV Info: 不支持{1}位,已更新成{2}位"
+//@@en="WAV Info: {1} bit is not supported, has been updated to {2} bit"
+,"wyw9:"+ //args: {1}-{2}
+       "WAV Info: El bit {1} no es compatible y se ha actualizado al bit {2}"
+
+]);
+//*************** End srcFile=engine/wav.js ***************
+
+
+
+//*************** Begin srcFile=extensions/buffer_stream.player.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="getAudioSrc方法已过时:请直接使用getMediaStream然后赋值给audio.srcObject,仅允许在不支持srcObject的浏览器中调用本方法赋值给audio.src以做兼容"
+//@@en="The getAudioSrc method is obsolete: please use getMediaStream directly and then assign it to audio.srcObject, it is only allowed to call this method in browsers that do not support srcObject and assign it to audio.src for compatibility"
+//@@Put0
+ "0XYC:"+ //no args
+       "El método getAudioSrc está obsoleto: utilice getMediaStream directamente y asígnelo a audio.srcObject. Solo se permite llamar a este método en navegadores que no admiten srcObject y asignarlo a audio.src por compatibilidad"
+
+//@@zh="start被stop终止"
+//@@en="start is terminated by stop"
+,"6DDt:"+ //no args
+       "start es cancelado por stop"
+
+//@@zh="{1}多次start"
+//@@en="{1} repeat start"
+,"I4h4:"+ //args: {1}
+       "{1} se repite start"
+
+//@@zh="浏览器不支持打开{1}"
+//@@en="The browser does not support opening {1}"
+,"P6Gs:"+ //args: {1}
+       "El navegador no admite la apertura de {1}"
+
+//@@zh="(注意:ctx不是running状态,start需要在用户操作(触摸、点击等)时进行调用,否则会尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)"
+//@@en=" (Note: ctx is not in the running state, start needs to be called when the user operates (touch, click, etc.), otherwise it will try to perform ctx.resume, which may cause compatibility issues (only iOS), please refer to the runningContext configuration in the document) "
+,"JwDm:"+ //no args
+       " (Nota: ctx no está en estado running. Es necesario llamar a start cuando el usuario opera (tocar, hacer clic, etc.); de lo contrario, se intentará ctx.resume, lo que puede causar problemas de compatibilidad (solo iOS). Consulte la configuración runningContext en el documento) "
+
+//@@zh="此浏览器的AudioBuffer实现不支持动态特性,采用兼容模式"
+//@@en="The AudioBuffer implementation of this browser does not support dynamic features, use compatibility mode"
+,"qx6X:"+ //no args
+       "La implementación AudioBuffer de este navegador no admite funciones dinámicas y utiliza el modo de compatibilidad"
+
+//@@zh="环境检测超时"
+//@@en="Environment detection timeout"
+,"cdOx:"+ //no args
+       "Tiempo de espera de detección del entorno"
+
+//@@zh="可能无法播放:{1}"
+//@@en="Could not play: {1}"
+,"S2Bu:"+ //args: {1}
+       "No puede jugar: {1}"
+
+//@@zh="input调用失败:非pcm[Int16,...]输入时,必须解码或者使用transform转换"
+//@@en="input call failed: non-pcm[Int16,...] input must be decoded or converted using transform"
+,"ZfGG:"+ //no args
+       "Falló la llamada input: no PCM [int16,...] al ingresar, se debe decodificar o usar la conversión transform"
+
+//@@zh="input调用失败:未提供sampleRate"
+//@@en="input call failed: sampleRate not provided"
+,"N4ke:"+ //no args
+       "Falló la llamada input: no se proporcionó sampleRate"
+
+//@@zh="input调用失败:data的sampleRate={1}和之前的={2}不同"
+//@@en="input call failed: sampleRate={1} of data is different from previous={2}"
+,"IHZd:"+ //args: {1}-{2}
+       "Falló la llamada a input: sampleRate={1} de los datos es diferente de la anterior ={2}"
+
+//@@zh="{1}未调用start方法"
+//@@en="{1} did not call the start method"
+,"TZPq:"+ //args: {1}
+       "{1} no se llama al método start"
+
+//@@zh="浏览器不支持音频解码"
+//@@en="Browser does not support audio decoding"
+,"iCFC:"+ //no args
+       "El navegador no admite decodificación de audio"
+
+//@@zh="音频解码数据必须是ArrayBuffer"
+//@@en="Audio decoding data must be ArrayBuffer"
+,"wE2k:"+ //no args
+       "Los datos de decodificación de audio deben ser ArrayBuffer"
+
+//@@zh="音频解码失败:{1}"
+//@@en="Audio decoding failed: {1}"
+,"mOaT:"+ //args: {1}
+       "Falló la decodificación de audio: {1}"
+
+]);
+//*************** End srcFile=extensions/buffer_stream.player.js ***************
+
+
+
+//*************** Begin srcFile=extensions/create-audio.nmn2pcm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="符号[{1}]无效:{2}"
+//@@en="Invalid symbol [{1}]: {2}"
+//@@Put0
+ "3RBa:"+ //args: {1}-{2}
+       "Símbolo [{1}] no válido: {2}"
+
+//@@zh="音符[{1}]无效:{2}"
+//@@en="Invalid note [{1}]: {2}"
+,"U212:"+ //args: {1}-{2}
+       "Nota [{1}] no válido: {2}"
+
+//@@zh="多个音时必须对齐,相差{1}ms"
+//@@en="Multiple tones must be aligned, with a difference of {1}ms"
+,"7qAD:"+ //args: {1}
+       "Hay que alinearse a la hora de múltiples sonidos, con diferencias {1}ms"
+
+//@@zh="祝你生日快乐"
+//@@en="Happy Birthday to You"
+,"QGsW:"+ //no args
+       "Happy Birthday to You"
+
+//@@zh="致爱丽丝"
+//@@en="For Elise"
+,"emJR:"+ //no args
+       "For Elise"
+
+//@@zh="卡农-右手简谱"
+//@@en="Canon - Right Hand Notation"
+,"GsYy:"+ //no args
+       "Canon - símbolo de la mano derecha"
+
+//@@zh="卡农"
+//@@en="Canon"
+,"bSFZ:"+ //no args
+       "Canon"
+
+]);
+//*************** End srcFile=extensions/create-audio.nmn2pcm.js ***************
+
+
+
+//*************** Begin srcFile=extensions/sonic.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="当前环境不支持Web Worker,不支持调用Sonic.Async"
+//@@en="The current environment does not support Web Worker and does not support calling Sonic.Async"
+//@@Put0
+ "Ikdz:"+ //no args
+       "El entorno actual no admite Web Worker, no admite llamadas a Sonic.Async"
+
+//@@zh="sonic worker剩{1}个未flush"
+//@@en="There are {1} unflushed sonic workers left"
+,"IC5Y:"+ //args: {1}
+       "sonic worker deja {1} sin flush"
+
+]);
+//*************** End srcFile=extensions/sonic.js ***************
+
+
+
+//*************** Begin srcFile=app-support/app-native-support.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="{1}中的{2}方法未实现,请在{3}文件中或配置文件中实现此方法"
+//@@en="The {2} method in {1} is not implemented, please implement this method in the {3} file or configuration file"
+//@@Put0
+ "WWoj:"+ //args: {1}-{3}
+       "El método {2} en {1} no se implementa, por favor implemente este método en el archivo {3} o en el archivo de configuración"
+
+//@@zh="未开始录音,但收到Native PCM数据"
+//@@en="Recording does not start, but Native PCM data is received"
+,"rCAM:"+ //no args
+       "No se inició la grabación, pero se recibieron los datos de Native PCM"
+
+//@@zh="检测到跨域iframe,NativeRecordReceivePCM无法注入到顶层,已监听postMessage转发兼容传输数据,请自行实现将top层接收到数据转发到本iframe(不限层),不然无法接收到录音数据"
+//@@en="A cross-domain iframe is detected. NativeRecordReceivePCM cannot be injected into the top layer. It has listened to postMessage to be compatible with data transmission. Please implement it by yourself to forward the data received by the top layer to this iframe (no limit on layer), otherwise the recording data cannot be received."
+,"t2OF:"+ //no args
+       "Detectado iframe entre dominios, NativeRecordReceivePCM no se puede inyectar en el nivel superior, se han monitoreado los datos de transmisión compatibles con el reenvío de postMessage, por favor, realice por sí mismo el reenvío de los datos recibidos en la capa superior a este iframe (capa ilimitada), de lo contrario no se pueden recibir los datos de grabación"
+
+//@@zh="未开始录音"
+//@@en="Recording not started"
+,"Z2y2:"+ //no args
+       "Grabación no iniciada"
+
+]);
+//*************** End srcFile=app-support/app-native-support.js ***************
+
+
+
+//*************** Begin srcFile=app-support/app.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="重复导入{1}"
+//@@en="Duplicate import {1}"
+//@@Put0
+ "uXtA:"+ //args: {1}
+       "Importación duplicada {1}"
+
+//@@zh="注意:因为并发调用了其他录音相关方法,当前 {1} 的调用结果已被丢弃且不会有回调"
+//@@en="Note: Because other recording-related methods are called concurrently, the current call result of {1} has been discarded and there will be no callback"
+,"kIBu:"+ //args: {1}
+       "Nota: Debido a que se llaman simultáneamente otros métodos relacionados con la grabación, el resultado de la llamada actual de {1} se ha descartado y no habrá devolución de llamada"
+
+//@@zh="重复注册{1}"
+//@@en="Duplicate registration {1}"
+,"ha2K:"+ //args: {1}
+       "Doble registro {1}"
+
+//@@zh="仅清理资源"
+//@@en="Clean resources only"
+,"wpTL:"+ //no args
+       "Solo limpiar recursos"
+
+//@@zh="未开始录音"
+//@@en="Recording not started"
+,"bpvP:"+ //no args
+       "Grabación no iniciada"
+
+//@@zh="当前环境不支持实时回调,无法进行{1}"
+//@@en="The current environment does not support real-time callback and cannot be performed {1}"
+,"fLJD:"+ //args: {1}
+       "El entorno actual no admite devoluciones de llamada en tiempo real y no se puede realizar {1}"
+
+//@@zh="录音权限请求失败:"
+//@@en="Recording permission request failed: "
+,"YnzX:"+ //no args
+       "La solicitud de permiso de grabación falló: "
+
+//@@zh="需先调用{1}"
+//@@en="Need to call {1} first"
+,"nwKR:"+ //args: {1}
+       "Primero hay que llamar a {1}"
+
+//@@zh="当前不是浏览器环境,需引入针对此平台的支持文件({1}),或调用{2}自行实现接入"
+//@@en="This is not a browser environment. You need to import support files for this platform ({1}), or call {2} to implement the access yourself."
+,"citA:"+ //args: {1}-{2}
+       "Actualmente no es un entorno de navegador, es necesario introducir un archivo de soporte para esta plataforma ({1}), o llamar al {2} para lograr su propio acceso."
+
+//@@zh="开始录音失败:"
+//@@en="Failed to start recording: "
+,"ecp9:"+ //no args
+       "Falló al iniciar la grabación: "
+
+//@@zh="不能录音:"
+//@@en="Cannot record: "
+,"EKmS:"+ //no args
+       "No se puede grabar: "
+
+//@@zh="已开始录音"
+//@@en="Recording started"
+,"k7Qo:"+ //no args
+       "Se ha iniciado la grabación"
+
+//@@zh="结束录音失败:"
+//@@en="Failed to stop recording: "
+,"Douz:"+ //no args
+       "Falló al finalizar la grabación: "
+
+//@@zh="和Start时差:{1}ms"
+//@@en="Time difference from Start: {1}ms"
+,"wqSH:"+ //args: {1}
+       "Diferencia horaria con start: {1}ms"
+
+//@@zh="结束录音 耗时{1}ms 音频时长{2}ms 文件大小{3}b {4}"
+//@@en="Stop recording, takes {1}ms, audio duration {2}ms, file size {3}b, {4}"
+,"g3VX:"+ //args: {1}-{4}
+       "Terminar la grabación lleva tiempo {1}ms , duración del audio {2}ms , tamaño del archivo {3}b , {4}"
+
+]);
+//*************** End srcFile=app-support/app.js ***************
+
+//@@User Code-2 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-2 End @@
+
+}));

+ 915 - 0
src/i18n/fr.js

@@ -0,0 +1,915 @@
+/*
+Recorder i18n/fr.js
+https://github.com/xiangyuecn/Recorder
+
+Usage: Recorder.i18n.lang="fr"
+
+Desc: French, Français, 法语。Cette traduction provient principalement de : traduction google + traduction Baidu, traduit du chinois vers le français. 此翻译主要来自:google翻译+百度翻译,由中文翻译成法语。
+
+注意:请勿修改//@@打头的文本行;以下代码结构由/src/package-i18n.js自动生成,只允许在字符串中填写翻译后的文本,请勿改变代码结构;翻译的文本如果需要明确的空值,请填写"=Empty";文本中的变量用{n}表示(n代表第几个变量),所有变量必须都出现至少一次,如果不要某变量用{n!}表示
+
+Note: Do not modify the text lines starting with //@@; The following code structure is automatically generated by /src/package-i18n.js, only the translated text is allowed to be filled in the string, please do not change the code structure; If the translated text requires an explicit empty value, please fill in "=Empty"; Variables in the text are represented by {n} (n represents the number of variables), all variables must appear at least once, if a variable is not required, it is represented by {n!}
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	factory(win.Recorder,browser);
+}(function(Recorder,isBrowser){
+"use strict";
+var i18n=Recorder.i18n;
+
+//@@User Code-1 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-1 End @@
+
+//@@Exec i18n.lang="fr";
+Recorder.CLog('Import Recorder i18n lang="fr"');
+
+//i18n.alias["other-lang-key"]="fr";
+
+var putSet={lang:"fr"};
+
+i18n.data["rtl$fr"]=false;
+i18n.data["desc$fr"]="French, Français, 法语。Cette traduction provient principalement de : traduction google + traduction Baidu, traduit du chinois vers le français. 此翻译主要来自:google翻译+百度翻译,由中文翻译成法语。";
+//@@Exec i18n.GenerateDisplayEnglish=true;
+
+
+
+//*************** Begin srcFile=recorder-core.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="重复导入{1}"
+//@@en="Duplicate import {1}"
+//@@Put0
+ "K8zP:"+ //args: {1}
+       "Répéter l'importation {1}"
+
+//@@zh="剩{1}个GetContext未close"
+//@@en="There are {1} GetContext unclosed"
+,"mSxV:"+ //args: {1}
+       "Les {1} GetContext restants n'ont pas été close"
+
+//@@zh="(注意:ctx不是running状态,rec.open和start至少要有一个在用户操作(触摸、点击等)时进行调用,否则将在rec.start时尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)"
+//@@en=" (Note: ctx is not in the running state. At least one of rec.open and start must be called during user operations (touch, click, etc.), otherwise ctx.resume will be attempted during rec.start, which may cause compatibility issues (iOS only), please refer to the runningContext configuration in the documentation) "
+,"nMIy:"+ //no args
+       " (Remarque : ctx n'est pas dans l'état running. Au moins l'un des rec.open et start doit être appelé pendant l'opération de l'utilisateur (toucher, cliquer, etc.), sinon ctx.resume sera tenté pendant rec.start, ce qui peut entraîner une compatibilité. problèmes (iOS uniquement), voir la configuration de runningContext dans la documentation) "
+
+//@@zh="。由于{1}内部1秒375次回调,在移动端可能会有性能问题导致回调丢失录音变短,PC端无影响,暂不建议开启{1}。"
+//@@en=". Due to 375 callbacks in 1 second in {1}, there may be performance problems on the mobile side, which may cause the callback to be lost and the recording to be shortened, but it will not affect the PC side. It is not recommended to enable {1} for now."
+,"ZGlf:"+ //args: {1}
+       ". En raison des 375 rappels par seconde dans un délai de {1}, il peut y avoir des problèmes de performances du côté mobile qui peuvent entraîner la perte des rappels et un raccourcissement de l'enregistrement. Il n'y a aucun impact du côté PC. Il n'est pas recommandé d'activer {1} pour le moment."
+
+//@@zh="Connect采用老的{1},"
+//@@en="Connect uses the old {1}, "
+,"7TU0:"+ //args: {1}
+       "Connect utilise l'ancien {1}, "
+
+//@@zh="但已设置{1}尝试启用{2}"
+//@@en="But {1} is set trying to enable {2}"
+,"JwCL:"+ //args: {1}-{2}
+       "Mais {1} est configuré pour essayer d'activer {2}"
+
+//@@zh="可设置{1}尝试启用{2}"
+//@@en="Can set {1} try to enable {2}"
+,"VGjB:"+ //args: {1}-{2}
+       "Vous pouvez configurer {1} pour essayer d'activer {2}"
+
+//@@zh="{1}未返回任何音频,恢复使用{2}"
+//@@en="{1} did not return any audio, reverting to {2}"
+,"MxX1:"+ //args: {1}-{2}
+       "{1} n'a renvoyé aucun son et a repris l'utilisation de {2}"
+
+//@@zh="{1}多余回调"
+//@@en="{1} redundant callback"
+,"XUap:"+ //args: {1}
+       "{1} rappel redondant"
+
+//@@zh="Connect采用{1},设置{2}可恢复老式{3}"
+//@@en="Connect uses {1}, set {2} to restore old-fashioned {3}"
+,"yOta:"+ //args: {1}-{3}
+       "Connect utilise {1} et la configuration de {2} peut restaurer l'ancien {3}"
+
+//@@zh="(此浏览器不支持{1})"
+//@@en=" (This browser does not support {1}) "
+,"VwPd:"+ //args: {1}
+       " (Ce navigateur ne prend pas en charge {1}) "
+
+//@@zh="{1}未返回任何音频,降级使用{2}"
+//@@en="{1} did not return any audio, downgrade to {2}"
+,"vHnb:"+ //args: {1}-{2}
+       "{1} ne renvoie aucun audio, rétrogradez pour utiliser {2}"
+
+//@@zh="{1}多余回调"
+//@@en="{1} redundant callback"
+,"O9P7:"+ //args: {1}
+       "{1} rappel redondant"
+
+//@@zh="Connect采用{1},设置{2}可恢复使用{3}或老式{4}"
+//@@en="Connect uses {1}, set {2} to restore to using {3} or old-fashioned {4}"
+,"LMEm:"+ //args: {1}-{4}
+       "Connect utilise {1}, la configuration de {2} peut restaurer l'utilisation de {3} ou de l'ancien {4}"
+
+//@@zh="{1}的filter采样率变了,重设滤波"
+//@@en="The filter sampleRate of {1} has changed, reset the filter"
+,"d48C:"+ //args: {1}
+       "Le taux d'échantillonnage du filtre de {1} a changé, réinitialisez le filtre"
+
+//@@zh="{1}似乎传入了未重置chunk {2}"
+//@@en="{1} seems to have passed in an unreset chunk {2}"
+,"tlbC:"+ //args: {1}-{2}
+       "{1} semble passer dans chunk {2} qui n'est pas réinitialisé"
+
+//@@zh="{1}和{2}必须是数值"
+//@@en="{1} and {2} must be number"
+,"VtS4:"+ //args: {1}-{2}
+       "{1} et {2} doivent être des valeurs numériques"
+
+//@@zh="录音open失败:"
+//@@en="Recording open failed: "
+,"5tWi:"+ //no args
+       "L'enregistrement de open a échoué : "
+
+//@@zh="open被取消"
+//@@en="open cancelled"
+,"dFm8:"+ //no args
+       "open a été annulé"
+
+//@@zh="open被中断"
+//@@en="open interrupted"
+,"VtJO:"+ //no args
+       "open a été interrompu"
+
+//@@zh=",可尝试使用RecordApp解决方案"
+//@@en=", you can try to use the RecordApp solution "
+,"EMJq:"+ //no args
+       ", vous pouvez essayer la solution RecordApp"
+
+//@@zh="不能录音:"
+//@@en="Cannot record: "
+,"A5bm:"+ //no args
+       "Impossible d'enregistrer : "
+
+//@@zh="不支持此浏览器从流中获取录音"
+//@@en="This browser does not support obtaining recordings from stream"
+,"1iU7:"+ //no args
+       "Ce navigateur ne prend pas en charge la récupération d'enregistrements à partir de flux"
+
+//@@zh="从流中打开录音失败:"
+//@@en="Failed to open recording from stream: "
+,"BTW2:"+ //no args
+       "Échec de l'ouverture de l'enregistrement à partir du flux : "
+
+//@@zh="无权录音(跨域,请尝试给iframe添加麦克风访问策略,如{1})"
+//@@en="No permission to record (cross domain, please try adding microphone access policy to iframe, such as: {1})"
+,"Nclz:"+ //args: {1}
+       "Aucune autorisation d'enregistrement (inter-domaine, veuillez essayer d'ajouter une stratégie d'accès au microphone à l'iframe, telle que {1})"
+
+//@@zh="用户拒绝了录音权限"
+//@@en="User denied recording permission"
+,"gyO5:"+ //no args
+       "L'utilisateur a refusé l'autorisation d'enregistrement"
+
+//@@zh="浏览器禁止不安全页面录音,可开启https解决"
+//@@en="Browser prohibits recording of unsafe pages, which can be resolved by enabling HTTPS"
+,"oWNo:"+ //no args
+       "Le navigateur interdit l'enregistrement des pages dangereuses, ce qui peut être résolu en activant https"
+
+//@@zh=",无可用麦克风"
+//@@en=", no microphone available"
+,"jBa9:"+ //no args
+       ", pas de micro disponible"
+
+//@@zh="此浏览器不支持录音"
+//@@en="This browser does not support recording"
+,"COxc:"+ //no args
+       "Ce navigateur ne prend pas en charge l'enregistrement"
+
+//@@zh="发现同时多次调用open"
+//@@en="It was found that open was called multiple times at the same time"
+,"upb8:"+ //no args
+       "J'ai constaté que open était appelé plusieurs fois en même temps"
+
+//@@zh="录音功能无效:无音频流"
+//@@en="Invalid recording: no audio stream"
+,"Q1GA:"+ //no args
+       "La fonction d'enregistrement ne fonctionne pas : pas de flux audio"
+
+//@@zh="请求录音权限错误"
+//@@en="Error requesting recording permission"
+,"xEQR:"+ //no args
+       "Erreur lors de la demande d'autorisation d'enregistrement"
+
+//@@zh="无法录音:"
+//@@en="Unable to record: "
+,"bDOG:"+ //no args
+       "Impossible d'enregistrer : "
+
+//@@zh=",未配置noiseSuppression和echoCancellation时浏览器可能会自动打开降噪和回声消除,移动端可能会降低系统播放音量(关闭录音后可恢复),请参阅文档中audioTrackSet配置"
+//@@en=", when noiseSuppression and echoCancellation are not configured, the browser may automatically enable noise suppression and echo cancellation, and the mobile terminal may reduce the system playback volume (recovery after the recording is closed), please refer to the audioTrackSet configuration in the document."
+,"RiWe:"+ //no args
+       ", lorsque noiseSuppression et echoCancellation ne sont pas configurés, le navigateur peut activer automatiquement la réduction du bruit et l'annulation de l'écho, et le terminal mobile peut réduire le volume de lecture du système (récupérable après avoir désactivé l'enregistrement), veuillez vous référer à la configuration audioTrackSet dans le document"
+
+//@@zh="close被忽略(因为同时open了多个rec,只有最后一个会真正close)"
+//@@en="close is ignored (because multiple recs are open at the same time, only the last one will actually close)"
+,"hWVz:"+ //no args
+       "close est ignoré (car plusieurs recs sont ouverts en même temps, seul le dernier sera en fait close)"
+
+//@@zh="忽略"
+//@@en="ignore"
+,"UHvm:"+ //no args
+       "négligence"
+
+//@@zh="不支持{1}架构"
+//@@en="{1} architecture not supported"
+,"Essp:"+ //args: {1}
+       "Ne prend pas en charge l'architecture {1}"
+
+//@@zh="{1}类型不支持设置takeoffEncodeChunk"
+//@@en="{1} type does not support setting takeoffEncodeChunk"
+,"2XBl:"+ //args: {1}
+       "Le type {1} ne prend pas en charge le paramètre takeoffEncodeChunk"
+
+//@@zh="(未加载编码器)"
+//@@en="(Encoder not loaded)"
+,"LG7e:"+ //no args
+       "(aucun encodeur chargé)"
+
+//@@zh="{1}环境不支持实时处理"
+//@@en="{1} environment does not support real-time processing"
+,"7uMV:"+ //args: {1}
+       "L'environnement {1} ne prend pas en charge le traitement en temps réel"
+
+//@@zh="补偿{1}ms"
+//@@en="Compensation {1}ms"
+,"4Kfd:"+ //args: {1}
+       "Compensation {1} ms"
+
+//@@zh="未补偿{1}ms"
+//@@en="Uncompensated {1}ms"
+,"bM5i:"+ //args: {1}
+       "Non compensé {1} ms"
+
+//@@zh="回调出错是不允许的,需保证不会抛异常"
+//@@en="Callback error is not allowed, you need to ensure that no exception will be thrown"
+,"gFUF:"+ //no args
+       "Les erreurs dans les rappels ne sont pas autorisées et doivent être assurées qu'aucune exception n'est levée"
+
+//@@zh="低性能,耗时{1}ms"
+//@@en="Low performance, took {1}ms"
+,"2ghS:"+ //args: {1}
+       "Faible performance, prend {1} ms"
+
+//@@zh="未进入异步前不能清除buffers"
+//@@en="Buffers cannot be cleared before entering async"
+,"ufqH:"+ //no args
+       "Les buffers ne peuvent pas être effacés avant d'entrer en mode asynchrone"
+
+//@@zh="start失败:未open"
+//@@en="start failed: not open"
+,"6WmN:"+ //no args
+       "Échec de start: pas open"
+
+//@@zh="start 开始录音,"
+//@@en="start recording, "
+,"kLDN:"+ //no args
+       "start, démarre l'enregistrement, "
+
+//@@zh="start被中断"
+//@@en="start was interrupted"
+,"Bp2y:"+ //no args
+       "start a été interrompu"
+
+//@@zh=",可能无法录音:"
+//@@en=", may fail to record: "
+,"upkE:"+ //no args
+       ", l'enregistrement peut ne pas être possible : "
+
+//@@zh="stop 和start时差:"
+//@@en="Stop and start time difference: "
+,"Xq4s:"+ //no args
+       "stop, décalage horaire avec start : "
+
+//@@zh="补偿:"
+//@@en="compensate: "
+,"3CQP:"+ //no args
+       "compenser: "
+
+//@@zh="结束录音失败:"
+//@@en="Failed to stop recording: "
+,"u8JG:"+ //no args
+       "Échec de la fin de l'enregistrement : "
+
+//@@zh=",请设置{1}"
+//@@en=", please set {1}"
+,"1skY:"+ //args: {1}
+       ", veuillez définir {1}"
+
+//@@zh="结束录音 编码花{1}ms 音频时长{2}ms 文件大小{3}b"
+//@@en="Stop recording, encoding takes {1}ms, audio duration {2}ms, file size {3}b"
+,"Wv7l:"+ //args: {1}-{3}
+       "Terminer l'enregistrement. L'encodage prend {1} ms. La durée audio est de {2} ms. La taille du fichier est de {3}b"
+
+//@@zh="{1}编码器返回的不是{2}"
+//@@en="{1} encoder returned not {2}"
+,"Vkbd:"+ //args: {1}-{2}
+       "L'encodeur {1} ne renvoie pas {2}"
+
+//@@zh="启用takeoffEncodeChunk后stop返回的blob长度为0不提供音频数据"
+//@@en="After enabling takeoffEncodeChunk, the length of the blob returned by stop is 0 and no audio data is provided"
+,"QWnr:"+ //no args
+       "Après avoir activé takeoffEncodeChunk, la longueur du blob renvoyée par stop est 0 et aucune donnée audio n'est fournie"
+
+//@@zh="生成的{1}无效"
+//@@en="Invalid generated {1}"
+,"Sz2H:"+ //args: {1}
+       "Le {1} généré n'est pas valide"
+
+//@@zh="未开始录音"
+//@@en="Recording not started"
+,"wf9t:"+ //no args
+       "L'enregistrement n'a pas démarré"
+
+//@@zh=",开始录音前无用户交互导致AudioContext未运行"
+//@@en=", No user interaction before starting recording, resulting in AudioContext not running"
+,"Dl2c:"+ //no args
+       ", il n'y a aucune interaction de l'utilisateur avant de démarrer l'enregistrement, ce qui empêche AudioContext de s'exécuter"
+
+//@@zh="未采集到录音"
+//@@en="Recording not captured"
+,"Ltz3:"+ //no args
+       "Aucun enregistrement n'a été collecté"
+
+//@@zh="未加载{1}编码器,请尝试到{2}的src/engine内找到{1}的编码器并加载"
+//@@en="The {1} encoder is not loaded. Please try to find the {1} encoder in the src/engine directory of the {2} and load it"
+,"xGuI:"+ //args: {1}-{2}
+       "L'encodeur {1} n'est pas chargé. Veuillez essayer de trouver l'encodeur {1} dans src/engine de {2} et de le charger"
+
+//@@zh="录音错误:"
+//@@en="Recording error: "
+,"AxOH:"+ //no args
+       "Erreur d'enregistrement : "
+
+//@@zh="音频buffers被释放"
+//@@en="Audio buffers are released"
+,"xkKd:"+ //no args
+       "Les tampons audio sont libérés"
+
+//@@zh="采样:{1} 花:{2}ms"
+//@@en="Sampled: {1}, took: {2}ms"
+,"CxeT:"+ //args: {1}-{2}
+       "Échantillon : {1} Fleur : {2}ms"
+
+//@@zh="非浏览器环境,不支持{1}"
+//@@en="Non-browser environment, does not support {1}"
+,"NonBrowser-1:"+ //args: {1}
+       "Environnement sans navigateur, ne prend pas en charge {1}"
+
+//@@zh="参数错误:{1}"
+//@@en="Illegal argument: {1}"
+,"IllegalArgs-1:"+ //args: {1}
+       "Erreur de paramètre : {1}"
+
+//@@zh="调用{1}需要先导入{2}"
+//@@en="Calling {1} needs to import {2} first"
+,"NeedImport-2:"+ //args: {1}-{2}
+       "Pour appeler {1}, vous devez d'abord importer {2}"
+
+//@@zh="不支持:{1}"
+//@@en="Not support: {1}"
+,"NotSupport-1:"+ //args: {1}
+       "Non pris en charge : {1}"
+
+//@@zh="覆盖导入{1}"
+//@@en="Override import {1}"
+,"8HO5:"+ //args: {1}
+       "Remplacer l'importation {1}"
+
+]);
+//*************** End srcFile=recorder-core.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-amr.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="AMR-NB(NarrowBand),采样率设置无效(只提供8000hz),比特率范围:{1}(默认12.2kbps),一帧20ms、{2}字节;浏览器一般不支持播放amr格式,可用Recorder.amr2wav()转码成wav播放"
+//@@en="AMR-NB (NarrowBand), sampleRate setting is invalid (only 8000hz is provided), bitRate range: {1} (default 12.2kbps), one frame 20ms, {2} bytes; browsers generally do not support playing amr format, available Recorder.amr2wav() transcoding into wav playback"
+//@@Put0
+ "b2mN:"+ //args: {1}-{2}
+       "AMR-NB (NarrowBand), le paramètre sampleRate n'est pas valide (seul 8000 Hz est fourni), plage bitRate : {1} (par défaut 12.2 kbps), une image 20 ms, {2} octets ; les navigateurs ne prennent généralement pas en charge la lecture du format amr, disponible Recorder.amr2wav() Transcoder en wav pour la lecture"
+
+//@@zh="AMR Info: 和设置的不匹配{1},已更新成{2}"
+//@@en="AMR Info: does not match the set {1}, has been updated to {2}"
+,"tQBv:"+ //args: {1}-{2}
+       "AMR Info : ne correspond pas à l'ensemble {1}, a été mis à jour vers {2}"
+
+//@@zh="数据采样率低于{1}"
+//@@en="Data sampleRate lower than {1}"
+,"q12D:"+ //args: {1}
+       "Les données sampleRate sont inférieures à {1}"
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+//@@en="The current browser version is too low to process in real time"
+,"TxjV:"+ //no args
+       "La version actuelle du navigateur est trop basse et ne peut pas être traitée en temps réel"
+
+//@@zh="takeoffEncodeChunk接管AMR编码器输出的二进制数据,只有首次回调数据(首帧)包含AMR头;在合并成AMR文件时,如果没有把首帧数据包含进去,则必须在文件开头添加上AMR头:Recorder.AMR.AMR_HEADER(转成二进制),否则无法播放"
+//@@en="takeoffEncodeChunk takes over the binary data output by the AMR encoder, and only the first callback data (the first frame) contains the AMR header; when merging into an AMR file, if the first frame data is not included, the AMR header must be added at the beginning of the file: Recorder.AMR.AMR_HEADER (converted to binary), otherwise it cannot be played"
+,"Q7p7:"+ //no args
+       "takeoffEncodeChunk prend en charge les données binaires sorties par l'encodeur AMR. Seules les premières données de rappel (première image) contiennent l'en-tête AMR ; lors de la fusion dans un fichier AMR, si les données de la première image ne sont pas incluses, l'en-tête AMR doit être ajouté à la début du fichier : Recorder.AMR.AMR_HEADER (converti en binaire), sinon il ne peut pas être lu"
+
+//@@zh="当前环境不支持Web Worker,amr实时编码器运行在主线程中"
+//@@en="The current environment does not support Web Worker, and the amr real-time encoder runs in the main thread"
+,"6o9Z:"+ //no args
+       "L'environnement actuel ne prend pas en charge Web Worker et l'encodeur en temps réel amr s'exécute dans le thread principal"
+
+//@@zh="amr worker剩{1}个未stop"
+//@@en="amr worker left {1} unstopped"
+,"yYWs:"+ //args: {1}
+       "amr worker reste {1} non stop"
+
+//@@zh="amr编码器未start"
+//@@en="amr encoder not started"
+,"jOi8:"+ //no args
+       "encodeur amr pas start"
+
+]);
+//*************** End srcFile=engine/beta-amr.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-ogg.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="Ogg Vorbis,比特率取值16-100kbps,采样率取值无限制"
+//@@en="Ogg Vorbis, bitRate 16-100kbps, sampleRate unlimited"
+//@@Put0
+ "O8Gn:"+ //no args
+       "Ogg Vorbis, la valeur bitRate est de 16 à 100 kbps, la valeur sampleRate est illimitée"
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+//@@en="The current browser version is too low to process in real time"
+,"5si6:"+ //no args
+       "La version actuelle du navigateur est trop basse et ne peut pas être traitée en temps réel"
+
+//@@zh="takeoffEncodeChunk接管OggVorbis编码器输出的二进制数据,Ogg由数据页组成,一页包含多帧音频数据(含几秒的音频,一页数据无法单独解码和播放),此编码器每次输出都是完整的一页数据,因此实时性会比较低;在合并成完整ogg文件时,必须将输出的所有数据合并到一起,否则可能无法播放,不支持截取中间一部分单独解码和播放"
+//@@en="takeoffEncodeChunk takes over the binary data output by the OggVorbis encoder. Ogg is composed of data pages. One page contains multiple frames of audio data (including a few seconds of audio, and one page of data cannot be decoded and played alone). This encoder outputs a complete page of data each time, so the real-time performance will be relatively low; when merging into a complete ogg file, all the output data must be merged together, otherwise it may not be able to play, and it does not support intercepting the middle part to decode and play separately"
+,"R8yz:"+ //no args
+       "takeoffEncodeChunk prend en charge les données binaires sorties par l'encodeur OggVorbis. Ogg est composé de pages de données. Une page contient plusieurs images de données audio (y compris plusieurs secondes d'audio. Une page de données ne peut pas être décodée et lue séparément). Chaque sortie de ce L'encodeur est complet. Une page de données, donc les performances en temps réel seront relativement faibles ; lors de la fusion dans un fichier ogg complet, toutes les données de sortie doivent être fusionnées ensemble, sinon elles risquent de ne pas être lues et ne sont pas prises en charge. interceptez la partie médiane, décodez-la et jouez-la séparément"
+
+//@@zh="当前环境不支持Web Worker,OggVorbis实时编码器运行在主线程中"
+//@@en="The current environment does not support Web Worker, and the OggVorbis real-time encoder runs in the main thread"
+,"hB9D:"+ //no args
+       "L'environnement actuel ne prend pas en charge les Web Workers et l'encodeur en temps réel OggVorbis s'exécute dans le thread principal."
+
+//@@zh="ogg worker剩{1}个未stop"
+//@@en="There are {1} unstopped ogg workers"
+,"oTiy:"+ //args: {1}
+       "ogg worker reste {1} non stop"
+
+//@@zh="ogg编码器未start"
+//@@en="ogg encoder not started"
+,"dIpw:"+ //no args
+       "encodeur ogg pas start"
+
+]);
+//*************** End srcFile=engine/beta-ogg.js ***************
+
+
+
+//*************** Begin srcFile=engine/beta-webm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="此浏览器不支持进行webm编码,未实现MediaRecorder"
+//@@en="This browser does not support webm encoding, MediaRecorder is not implemented"
+//@@Put0
+ "L49q:"+ //no args
+       "Ce navigateur ne prend pas en charge l'encodage Webm et MediaRecorder n'est pas implémenté"
+
+//@@zh="只有比较新的浏览器支持,压缩率和mp3差不多。由于未找到对已有pcm数据进行快速编码的方法,只能按照类似边播放边收听形式把数据导入到MediaRecorder,有几秒就要等几秒。输出音频虽然可以通过比特率来控制文件大小,但音频文件中的比特率并非设定比特率,采样率由于是我们自己采样的,到这个编码器随他怎么搞"
+//@@en="Only newer browsers support it, and the compression rate is similar to mp3. Since there is no way to quickly encode the existing pcm data, the data can only be imported into MediaRecorder in a similar manner while playing and listening, and it takes a few seconds to wait for a few seconds. Although the output audio can control the file size through the bitRate, the bitRate in the audio file is not the set bitRate. Since the sampleRate is sampled by ourselves, we can do whatever we want with this encoder."
+,"tsTW:"+ //no args
+       "Seuls les navigateurs les plus récents le prennent en charge et le taux de compression est similaire à celui du mp3. Puisqu'il n'existe aucun moyen d'encoder rapidement les données pcm existantes, les données ne peuvent être importées dans MediaRecorder que d'une manière similaire de lecture et d'écoute, et vous devez attendre quelques secondes. Bien que la taille du fichier audio de sortie puisse être contrôlée par le débit binaire, le débit binaire du fichier audio n'est pas le débit binaire défini. Puisque le taux d'échantillonnage est échantillonné par nous-mêmes, nous pouvons faire ce que nous voulons avec cet encodeur"
+
+//@@zh="此浏览器不支持把录音转成webm格式"
+//@@en="This browser does not support converting recordings to webm format"
+,"aG4z:"+ //no args
+       "Ce navigateur ne prend pas en charge la conversion des enregistrements au format Webm"
+
+//@@zh="转码webm出错:{1}"
+//@@en="Error encoding webm: {1}"
+,"PIX0:"+ //args: {1}
+       "Erreur de transcodage Webm : {1}"
+
+]);
+//*************** End srcFile=engine/beta-webm.js ***************
+
+
+
+//*************** Begin srcFile=engine/g711x.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="{1};{2}音频文件无法直接播放,可用Recorder.{2}2wav()转码成wav播放;采样率比特率设置无效,固定为8000hz采样率、16位,每个采样压缩成8位存储,音频文件大小为8000字节/秒;如需任意采样率支持,请使用Recorder.{2}_encode()方法"
+//@@en="{1}; {2} audio files cannot be played directly, and can be transcoded into wav by Recorder.{2}2wav(); the sampleRate bitRate setting is invalid, fixed at 8000hz sampleRate, 16 bits, each sample is compressed into 8 bits for storage, and the audio file size is 8000 bytes/second; if you need any sampleRate support, please use Recorder.{2}_encode() Method"
+//@@Put0
+ "d8YX:"+ //args: {1}-{2}
+       "{1} ; {2} Le fichier audio ne peut pas être lu directement. Vous pouvez utiliser Recorder.{2}2wav() pour le transcoder en wav pour la lecture ; le paramètre de débit binaire du taux d'échantillonnage n'est pas valide et est fixé à 8 000 hz. taux, 16 bits, et chaque échantillon est compressé. dans un stockage de 8 bits, la taille du fichier audio est de 8 000 octets/seconde ; si vous avez besoin de prise en charge pour un taux d'échantillonnage, veuillez utiliser la méthode Recorder.{2}_encode()"
+
+//@@zh="数据采样率低于{1}"
+//@@en="Data sampleRate lower than {1}"
+,"29UK:"+ //args: {1}
+       "Les données sampleRate sont inférieures à {1}"
+
+//@@zh="{1}编码器未start"
+//@@en="{1} encoder not started"
+,"quVJ:"+ //args: {1}
+       "encodeur {1} pas start"
+
+]);
+//*************** End srcFile=engine/g711x.js ***************
+
+
+
+//*************** Begin srcFile=engine/mp3.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="采样率范围:{1};比特率范围:{2}(不同比特率支持的采样率范围不同,小于32kbps时采样率需小于32000)"
+//@@en="sampleRate range: {1}; bitRate range: {2} (the sampleRate range supported by different bitRate is different, when the bitRate is less than 32kbps, the sampleRate must be less than 32000)"
+//@@Put0
+ "Zm7L:"+ //args: {1}-{2}
+       "Plage sampleRate : {1} ; plage bitRate : {2} (différents bitRate prennent en charge différentes plages sampleRate. Lorsqu'il est inférieur à 32 kbit/s, le sampleRate doit être inférieur à 32000)"
+
+//@@zh="{1}不在mp3支持的取值范围:{2}"
+//@@en="{1} is not in the value range supported by mp3: {2}"
+,"eGB9:"+ //args: {1}-{2}
+       "{1} n'est pas dans la plage de valeurs prise en charge par mp3 : {2}"
+
+//@@zh="sampleRate已更新为{1},因为{2}不在mp3支持的取值范围:{3}"
+//@@en="sampleRate has been updated to {1}, because {2} is not in the value range supported by mp3: {3}"
+,"zLTa:"+ //args: {1}-{3}
+       "sampleRate a été mis à jour à {1} car {2} n'est pas dans la plage de valeurs prise en charge par mp3 : {3}"
+
+//@@zh="当前浏览器版本太低,无法实时处理"
+//@@en="The current browser version is too low to process in real time"
+,"yhUs:"+ //no args
+       "La version actuelle du navigateur est trop basse et ne peut pas être traitée en temps réel"
+
+//@@zh="当前环境不支持Web Worker,mp3实时编码器运行在主线程中"
+//@@en="The current environment does not support Web Worker, and the mp3 real-time encoder runs in the main thread"
+,"k9PT:"+ //no args
+       "L'environnement actuel ne prend pas en charge Web Worker et l'encodeur en temps réel mp3 s'exécute dans le thread principal"
+
+//@@zh="mp3 worker剩{1}个未stop"
+//@@en="There are {1} unstopped mp3 workers left"
+,"fT6M:"+ //args: {1}
+       "mp3 worker left {1} unstopped"
+
+//@@zh="mp3编码器未start"
+//@@en="mp3 encoder not started"
+,"mPxH:"+ //no args
+       "encodeur mp3 pas start"
+
+//@@zh="和设置的不匹配{1},已更新成{2}"
+//@@en="Does not match the set {1}, has been updated to {2}"
+,"uY9i:"+ //args: {1}-{2}
+       "Ne correspond pas au paramètre {1}, a été mis à jour vers {2}"
+
+//@@zh="Fix移除{1}帧"
+//@@en="Fix remove {1} frame"
+,"iMSm:"+ //args: {1}
+       "Fix supprime {1} images"
+
+//@@zh="移除帧数过多"
+//@@en="Remove too many frames"
+,"b9zm:"+ //no args
+       "Supprimer trop de cadres"
+
+]);
+//*************** End srcFile=engine/mp3.js ***************
+
+
+
+//*************** Begin srcFile=engine/pcm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="pcm为未封装的原始音频数据,pcm音频文件无法直接播放,可用Recorder.pcm2wav()转码成wav播放;支持位数8位、16位(填在比特率里面),采样率取值无限制"
+//@@en="pcm is unencapsulated original audio data, pcm audio files cannot be played directly, and can be transcoded into wav by Recorder.pcm2wav(); it supports 8-bit and 16-bit bits (fill in the bitRate), and the sampleRate is unlimited"
+//@@Put0
+ "fWsN:"+ //no args
+       "pcm est une donnée audio originale non encapsulée. Les fichiers audio Pcm ne peuvent pas être lus directement. Recorder.pcm2wav() peut être utilisé pour transcoder en wav pour la lecture. Il prend en charge 8 et 16 chiffres (remplis dans bitRate) et la valeur de sampleRate est illimitée."
+
+//@@zh="PCM Info: 不支持{1}位,已更新成{2}位"
+//@@en="PCM Info: {1} bit is not supported, has been updated to {2} bit"
+,"uMUJ:"+ //args: {1}-{2}
+       "PCM Info: Le bit {1} n'est pas pris en charge et a été mis à jour vers le bit {2}"
+
+//@@zh="pcm2wav必须提供sampleRate和bitRate"
+//@@en="pcm2wav must provide sampleRate and bitRate"
+,"KmRz:"+ //no args
+       "pcm2wav doit fournir sampleRate et bitRate"
+
+//@@zh="pcm编码器未start"
+//@@en="pcm encoder not started"
+,"sDkA:"+ //no args
+       "encodeur pcm pas start"
+
+]);
+//*************** End srcFile=engine/pcm.js ***************
+
+
+
+//*************** Begin srcFile=engine/wav.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="支持位数8位、16位(填在比特率里面),采样率取值无限制;此编码器仅在pcm数据前加了一个44字节的wav头,编码出来的16位wav文件去掉开头的44字节即可得到pcm(注:其他wav编码器可能不是44字节)"
+//@@en="Supports 8-bit and 16-bit bits (fill in the bitRate), and the sampleRate is unlimited; this encoder only adds a 44-byte wav header before the pcm data, and the encoded 16-bit wav file removes the beginning 44 bytes to get pcm (note: other wav encoders may not be 44 bytes)"
+//@@Put0
+ "gPSE:"+ //no args
+       "Prend en charge les chiffres de 8 bits et 16 bits (remplis dans bitRate) et la valeur de sampleRate est illimitée ; cet encodeur ajoute uniquement un en-tête wav de 44 octets avant les données pcm, et le fichier wav codé de 16 bits supprime les 44 premiers bits. Octets pour obtenir pcm (remarque : les autres encodeurs wav peuvent ne pas faire 44 octets)"
+
+//@@zh="WAV Info: 不支持{1}位,已更新成{2}位"
+//@@en="WAV Info: {1} bit is not supported, has been updated to {2} bit"
+,"wyw9:"+ //args: {1}-{2}
+       "WAV Info: Le bit {1} n'est pas pris en charge et a été mis à jour vers le bit {2}"
+
+]);
+//*************** End srcFile=engine/wav.js ***************
+
+
+
+//*************** Begin srcFile=extensions/buffer_stream.player.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="getAudioSrc方法已过时:请直接使用getMediaStream然后赋值给audio.srcObject,仅允许在不支持srcObject的浏览器中调用本方法赋值给audio.src以做兼容"
+//@@en="The getAudioSrc method is obsolete: please use getMediaStream directly and then assign it to audio.srcObject, it is only allowed to call this method in browsers that do not support srcObject and assign it to audio.src for compatibility"
+//@@Put0
+ "0XYC:"+ //no args
+       "La méthode getAudioSrc est obsolète : veuillez utiliser getMediaStream directement et l'attribuer à audio.srcObject. Cette méthode ne peut être appelée que dans les navigateurs qui ne prennent pas en charge srcObject et attribuée à audio.src pour des raisons de compatibilité."
+
+//@@zh="start被stop终止"
+//@@en="start is terminated by stop"
+,"6DDt:"+ //no args
+       "start est terminé par stop"
+
+//@@zh="{1}多次start"
+//@@en="{1} repeat start"
+,"I4h4:"+ //args: {1}
+       "Répétition {1} start"
+
+//@@zh="浏览器不支持打开{1}"
+//@@en="The browser does not support opening {1}"
+,"P6Gs:"+ //args: {1}
+       "Le navigateur ne prend pas en charge l'ouverture de {1}"
+
+//@@zh="(注意:ctx不是running状态,start需要在用户操作(触摸、点击等)时进行调用,否则会尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)"
+//@@en=" (Note: ctx is not in the running state, start needs to be called when the user operates (touch, click, etc.), otherwise it will try to perform ctx.resume, which may cause compatibility issues (only iOS), please refer to the runningContext configuration in the document) "
+,"JwDm:"+ //no args
+       "(Remarque : ctx n'est pas dans l'état running. start doit être appelé lorsque l'utilisateur opère (toucher, cliquer, etc.), sinon ctx.resume sera tenté, ce qui peut entraîner des problèmes de compatibilité (iOS uniquement). Veuillez vous référer au configuration runningContext dans le document)"
+
+//@@zh="此浏览器的AudioBuffer实现不支持动态特性,采用兼容模式"
+//@@en="The AudioBuffer implementation of this browser does not support dynamic features, use compatibility mode"
+,"qx6X:"+ //no args
+       "L'implémentation AudioBuffer de ce navigateur ne prend pas en charge les fonctionnalités dynamiques et utilise le mode de compatibilité"
+
+//@@zh="环境检测超时"
+//@@en="Environment detection timeout"
+,"cdOx:"+ //no args
+       "Expiration du délai de détection de l'environnement"
+
+//@@zh="可能无法播放:{1}"
+//@@en="Could not play: {1}"
+,"S2Bu:"+ //args: {1}
+       "Peut ne pas jouer: {1}"
+
+//@@zh="input调用失败:非pcm[Int16,...]输入时,必须解码或者使用transform转换"
+//@@en="input call failed: non-pcm[Int16,...] input must be decoded or converted using transform"
+,"ZfGG:"+ //no args
+       "L'appel input a échoué: non - PCM [int16,...] en entrée, il doit être décodé ou converti avec transform"
+
+//@@zh="input调用失败:未提供sampleRate"
+//@@en="input call failed: sampleRate not provided"
+,"N4ke:"+ //no args
+       "L'appel input a échoué: sampleRate n'a pas été fourni"
+
+//@@zh="input调用失败:data的sampleRate={1}和之前的={2}不同"
+//@@en="input call failed: sampleRate={1} of data is different from previous={2}"
+,"IHZd:"+ //args: {1}-{2}
+       "L'appel input a échoué: sampleRate={1} de Data est différent de ={2} précédent"
+
+//@@zh="{1}未调用start方法"
+//@@en="{1} did not call the start method"
+,"TZPq:"+ //args: {1}
+       "{1} la méthode start n'est pas appelée"
+
+//@@zh="浏览器不支持音频解码"
+//@@en="Browser does not support audio decoding"
+,"iCFC:"+ //no args
+       "Le navigateur ne supporte pas le décodage audio"
+
+//@@zh="音频解码数据必须是ArrayBuffer"
+//@@en="Audio decoding data must be ArrayBuffer"
+,"wE2k:"+ //no args
+       "Les données de décodage audio doivent être ArrayBuffer"
+
+//@@zh="音频解码失败:{1}"
+//@@en="Audio decoding failed: {1}"
+,"mOaT:"+ //args: {1}
+       "Le décodage audio a échoué: {1}"
+
+]);
+//*************** End srcFile=extensions/buffer_stream.player.js ***************
+
+
+
+//*************** Begin srcFile=extensions/create-audio.nmn2pcm.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="符号[{1}]无效:{2}"
+//@@en="Invalid symbol [{1}]: {2}"
+//@@Put0
+ "3RBa:"+ //args: {1}-{2}
+       "Le symbole [{1}] est invalide: {2}"
+
+//@@zh="音符[{1}]无效:{2}"
+//@@en="Invalid note [{1}]: {2}"
+,"U212:"+ //args: {1}-{2}
+       "Note [{1}] invalide: {2}"
+
+//@@zh="多个音时必须对齐,相差{1}ms"
+//@@en="Multiple tones must be aligned, with a difference of {1}ms"
+,"7qAD:"+ //args: {1}
+       "Doit être aligné lorsque plusieurs tonalités, différence {1}ms"
+
+//@@zh="祝你生日快乐"
+//@@en="Happy Birthday to You"
+,"QGsW:"+ //no args
+       "Happy Birthday to You"
+
+//@@zh="致爱丽丝"
+//@@en="For Elise"
+,"emJR:"+ //no args
+       "For Elise"
+
+//@@zh="卡农-右手简谱"
+//@@en="Canon - Right Hand Notation"
+,"GsYy:"+ //no args
+       "Canon - symbole de la main droite"
+
+//@@zh="卡农"
+//@@en="Canon"
+,"bSFZ:"+ //no args
+       "Canon"
+
+]);
+//*************** End srcFile=extensions/create-audio.nmn2pcm.js ***************
+
+
+
+//*************** Begin srcFile=extensions/sonic.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="当前环境不支持Web Worker,不支持调用Sonic.Async"
+//@@en="The current environment does not support Web Worker and does not support calling Sonic.Async"
+//@@Put0
+ "Ikdz:"+ //no args
+       "Web Worker n'est pas supporté dans l'environnement actuel, appel Sonic.Async n'est pas supporté"
+
+//@@zh="sonic worker剩{1}个未flush"
+//@@en="There are {1} unflushed sonic workers left"
+,"IC5Y:"+ //args: {1}
+       "sonic worker reste {1} non flush"
+
+]);
+//*************** End srcFile=extensions/sonic.js ***************
+
+
+
+//*************** Begin srcFile=app-support/app-native-support.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="{1}中的{2}方法未实现,请在{3}文件中或配置文件中实现此方法"
+//@@en="The {2} method in {1} is not implemented, please implement this method in the {3} file or configuration file"
+//@@Put0
+ "WWoj:"+ //args: {1}-{3}
+       "La méthode {2} dans {1} n'est pas implémentée, implémentez - la dans un fichier {3} ou dans un fichier de configuration"
+
+//@@zh="未开始录音,但收到Native PCM数据"
+//@@en="Recording does not start, but Native PCM data is received"
+,"rCAM:"+ //no args
+       "L'enregistrement n'a pas commencé, mais les données Native PCM ont été reçues"
+
+//@@zh="检测到跨域iframe,NativeRecordReceivePCM无法注入到顶层,已监听postMessage转发兼容传输数据,请自行实现将top层接收到数据转发到本iframe(不限层),不然无法接收到录音数据"
+//@@en="A cross-domain iframe is detected. NativeRecordReceivePCM cannot be injected into the top layer. It has listened to postMessage to be compatible with data transmission. Please implement it by yourself to forward the data received by the top layer to this iframe (no limit on layer), otherwise the recording data cannot be received."
+,"t2OF:"+ //no args
+       "Iframe Cross - Domain détecté, NativeRecordReceivePCM ne peut pas être injecté à la couche supérieure, a écouté postMessage pour transmettre des données de transfert compatibles, s'il vous plaît implémenter vous - même pour transmettre les données reçues à la couche supérieure à cette iframe (couche illimitée), sinon les données d'enregistrement ne peuvent pas être reçues"
+
+//@@zh="未开始录音"
+//@@en="Recording not started"
+,"Z2y2:"+ //no args
+       "L'enregistrement n'a pas commencé"
+
+]);
+//*************** End srcFile=app-support/app-native-support.js ***************
+
+
+
+//*************** Begin srcFile=app-support/app.js ***************
+i18n.put(putSet,
+[ //@@PutList 
+
+//@@zh="重复导入{1}"
+//@@en="Duplicate import {1}"
+//@@Put0
+ "uXtA:"+ //args: {1}
+       "Importation répétée {1}"
+
+//@@zh="注意:因为并发调用了其他录音相关方法,当前 {1} 的调用结果已被丢弃且不会有回调"
+//@@en="Note: Because other recording-related methods are called concurrently, the current call result of {1} has been discarded and there will be no callback"
+,"kIBu:"+ //args: {1}
+       "Remarque : Étant donné que d'autres méthodes liées à l'enregistrement sont appelées simultanément, le résultat de l'appel actuel de {1} a été ignoré et il n'y aura pas de rappel"
+
+//@@zh="重复注册{1}"
+//@@en="Duplicate registration {1}"
+,"ha2K:"+ //args: {1}
+       "Enregistrement répété {1}"
+
+//@@zh="仅清理资源"
+//@@en="Clean resources only"
+,"wpTL:"+ //no args
+       "Nettoyage des ressources uniquement"
+
+//@@zh="未开始录音"
+//@@en="Recording not started"
+,"bpvP:"+ //no args
+       "L'enregistrement n'a pas commencé"
+
+//@@zh="当前环境不支持实时回调,无法进行{1}"
+//@@en="The current environment does not support real-time callback and cannot be performed {1}"
+,"fLJD:"+ //args: {1}
+       "L'environnement actuel ne prend pas en charge le Callback en temps réel, impossible de faire {1}"
+
+//@@zh="录音权限请求失败:"
+//@@en="Recording permission request failed: "
+,"YnzX:"+ //no args
+       "La demande d'autorisation d'enregistrement a échoué: "
+
+//@@zh="需先调用{1}"
+//@@en="Need to call {1} first"
+,"nwKR:"+ //args: {1}
+       "Appelez d'abord {1}"
+
+//@@zh="当前不是浏览器环境,需引入针对此平台的支持文件({1}),或调用{2}自行实现接入"
+//@@en="This is not a browser environment. You need to import support files for this platform ({1}), or call {2} to implement the access yourself."
+,"citA:"+ //args: {1}-{2}
+       "Actuellement, ce n'est pas un environnement de navigateur, il est nécessaire d'introduire un fichier de support ({1}) pour cette plate - forme ou d'appeler {2} pour implémenter l'accès par vous - même"
+
+//@@zh="开始录音失败:"
+//@@en="Failed to start recording: "
+,"ecp9:"+ //no args
+       "Le début de l'enregistrement échoue: "
+
+//@@zh="不能录音:"
+//@@en="Cannot record: "
+,"EKmS:"+ //no args
+       "Ne peut pas enregistrer: "
+
+//@@zh="已开始录音"
+//@@en="Recording started"
+,"k7Qo:"+ //no args
+       "Enregistrement commencé"
+
+//@@zh="结束录音失败:"
+//@@en="Failed to stop recording: "
+,"Douz:"+ //no args
+       "Fin de l'enregistrement échoué: "
+
+//@@zh="和Start时差:{1}ms"
+//@@en="Time difference from Start: {1}ms"
+,"wqSH:"+ //args: {1}
+       "Et le décalage horaire de départ: {1}ms"
+
+//@@zh="结束录音 耗时{1}ms 音频时长{2}ms 文件大小{3}b {4}"
+//@@en="Stop recording, takes {1}ms, audio duration {2}ms, file size {3}b, {4}"
+,"g3VX:"+ //args: {1}-{4}
+       "Fin de l'enregistrement, prend du temps {1}ms Durée audio {2}ms , taille du fichier {3}b , {4}"
+
+]);
+//*************** End srcFile=app-support/app.js ***************
+
+//@@User Code-2 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-2 End @@
+
+}));

+ 41 - 0
src/i18n/zh-CN.js

@@ -0,0 +1,41 @@
+/*
+Recorder i18n/zh-CN.js
+https://github.com/xiangyuecn/Recorder
+
+Usage: Recorder.i18n.lang="zh-CN" or "zh"
+
+Desc: Simplified Chinese, 简体中文。代码内置完整的中文支持,无需额外翻译,本文件存在的意义是方便查看支持的语言。
+
+注意:请勿修改//@@打头的文本行;以下代码结构由/src/package-i18n.js自动生成,只允许在字符串中填写翻译后的文本,请勿改变代码结构;翻译的文本如果需要明确的空值,请填写"=Empty";文本中的变量用{n}表示(n代表第几个变量),所有变量必须都出现至少一次,如果不要某变量用{n!}表示
+
+Note: Do not modify the text lines starting with //@@; The following code structure is automatically generated by /src/package-i18n.js, only the translated text is allowed to be filled in the string, please do not change the code structure; If the translated text requires an explicit empty value, please fill in "=Empty"; Variables in the text are represented by {n} (n represents the number of variables), all variables must appear at least once, if a variable is not required, it is represented by {n!}
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	factory(win.Recorder,browser);
+}(function(Recorder,isBrowser){
+"use strict";
+var i18n=Recorder.i18n;
+
+//@@User Code-1 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-1 End @@
+
+//@@Exec i18n.lang="zh-CN";
+Recorder.CLog('Import Recorder i18n lang="zh-CN"');
+
+i18n.alias["zh-CN"]="zh";
+
+var putSet={lang:"zh"};
+
+i18n.data["rtl$zh"]=false;
+i18n.data["desc$zh"]="Simplified Chinese, 简体中文。代码内置完整的中文支持,无需额外翻译,本文件存在的意义是方便查看支持的语言。";
+//@@Exec i18n.GenerateDisplayEnglish=false;
+//@@Exec i18n.put(putSet,[]);
+
+//@@User Code-2 Begin 手写代码放这里 Put the handwritten code here @@
+
+//@@User Code-2 End @@
+
+}));

+ 275 - 0
src/package-build.js

@@ -0,0 +1,275 @@
+/*
+js压缩合并用的nodejs代码
+
+源码内支持编译指令(去掉*前后的空格):
+	```
+	/ *=:=* /
+		源码时执行的js代码
+	/ *<@
+		编译后执行的js代码
+	@>* /
+	```
+*/
+
+var fs = require("fs");
+var crypto = require('crypto');
+var UglifyJS = require("uglify-js");
+var i18nRun = require("./package-i18n.js");
+
+console.log("\x1B[32m%s\x1B[0m","请选择操作:(1)生成语言包 (2)压缩js (3)生成npm包 (回车)所有");
+process.stdin.on('data',function(input){
+	input=input.toString().trim();
+	if(!input||input=="1"){
+		i18nRun();
+	};
+	if(!input||input=="2"){
+		Run_minify();
+	};
+	if(!input||input=="3"){
+		Run_npm();
+	};
+	console.log("\x1B[33m%s\x1B[0m","程序已退出");
+	process.exit();
+});
+
+
+
+
+function Run_minify(){
+	deleteFolder("../dist");
+	!fs.existsSync("../dist")&&fs.mkdirSync("../dist");
+	fs.mkdirSync("../dist/engine");
+	fs.mkdirSync("../dist/extensions");
+	fs.mkdirSync("../dist/i18n");
+	fs.mkdirSync("../dist/app-support");
+
+	console.log("\x1B[33m%s\x1B[0m","开始minify处理文件...");
+
+
+	minify("../recorder.mp3.min.js",["recorder-core.js","engine/mp3.js","engine/mp3-engine.js"]);
+	minify("../recorder.wav.min.js",["recorder-core.js","engine/wav.js"]);
+
+	minify("../dist/recorder-core.js",["recorder-core.js"]);
+	minify("../dist/engine/mp3.js",["engine/mp3.js","engine/mp3-engine.js"]);
+	minify("../dist/engine/wav.js",["engine/wav.js"]);
+	minify("../dist/engine/pcm.js",["engine/pcm.js"]);
+	minify("../dist/engine/g711x.js",["engine/g711x.js"]);
+
+	minify("../dist/engine/beta-webm.js",["engine/beta-webm.js"]);
+	minify("../dist/engine/beta-ogg.js",["engine/beta-ogg.js","engine/beta-ogg-engine.js"]);
+	minify("../dist/engine/beta-amr.js",["engine/beta-amr.js","engine/beta-amr-engine.js","engine/wav.js"]);
+
+	fs.readdirSync("app-support").forEach(function(file){
+		minify("../dist/app-support/"+file,["app-support/"+file]);
+	});
+	fs.readdirSync("extensions").forEach(function(file){
+		minify("../dist/extensions/"+file,["extensions/"+file]);
+	});
+	fs.readdirSync("i18n").forEach(function(file){
+		minify("../dist/i18n/"+file,["i18n/"+file]);
+	});
+
+	console.log("\x1B[33m%s\x1B[0m","处理完成");
+};
+
+
+function minify(output,srcs){
+	console.log("正在生成"+output);
+	var codes=[];
+	for(var i=0;i<srcs.length;i++){
+		codes.push(fs.readFileSync(srcs[i],"utf-8"));
+	};
+	var code=codes.join("\n").replace(
+		/\/\*=:=\*\/([\S\s]+?)\/\*<@([\S\s]+?)@>\*\//g
+		,function(a,b,c){
+			console.log("*******使用编译指令:\n"+a+"\n\n");
+			return c;
+		});
+	
+	var res=UglifyJS.minify(code);
+	if(res.error){
+		throw new Error(res.error);
+	};
+	
+	code=
+`/*
+录音
+https://github.com/xiangyuecn/Recorder
+src: ${srcs.join(",")}
+*/
+`;
+	code+=res.code;
+	fs.writeFileSync(output,code);
+	
+	//asm.js代码检测,压缩可能丢掉参数类型:function xx(a,b){a|0;b|0;
+	if(/['"]use\s+asm['"]/.test(code)){
+		var exp=/function\s+(\w+)\s*\([^\)]+\)\s*([\{;]\w\|0\b)+[^\}]*\}/g,m;
+		while(m=exp.exec(code)){
+			console.log("\x1B[31m%s\x1B[0m","        asm代码压缩后参数错误:"+m[0]+" 可能是没有任何引用导致的,尝试源码中删除这个函数");
+		}
+	}
+};
+
+
+
+
+
+
+function deleteFolder(path,deep){
+	if(fs.existsSync(path)){
+		var files=fs.readdirSync(path);
+		files.forEach(function(file){
+			var p=path+"/"+file;
+			if(fs.statSync(p).isDirectory()){
+				deleteFolder(p,(deep||0)+1);
+			} else {
+				fs.unlinkSync(p);
+			}
+		});
+		if(deep){
+			fs.rmdirSync(path);
+		};
+	};
+};
+
+
+
+
+
+function MDAbsImg(txt,baseUrl){//简单的图片地址 相对路径改成绝对路径
+	return txt.replace(/\!\[\]\((.+?)\)/g,function(a,url){
+		var folder=baseUrl;
+		if(!/^https?:/i.test(url)){
+			while(/^\.\.\/(.*)$/.test(url)){
+				url=RegExp.$1;
+				folder=folder.replace(/\/[^\/]+\/$/,"/");
+			}
+			url=folder+url;
+		}
+		return '![]('+url+')';
+	});
+};
+
+function Run_npm(){
+	console.log("\x1B[33m%s\x1B[0m","制作作者需要上传的npm包文件...");
+	var npmHome="../assets/npm-home";
+	var npmFiles=npmHome+"/npm-files";
+	var npmSrc=npmFiles+"/src";
+	deleteFolder(npmFiles);
+	!fs.existsSync(npmFiles)&&fs.mkdirSync(npmFiles);
+	fs.mkdirSync(npmSrc);
+	var srcDirs=["engine","extensions","i18n","app-support"];
+
+	var rootREADME=fs.readFileSync("../README.md","utf-8");
+	var appREADME=fs.readFileSync("../app-support-sample/README.md","utf-8");
+	var npmREADME=fs.readFileSync(npmHome+"/README.md","utf-8");
+	rootREADME=MDAbsImg(rootREADME,"https://xiangyuecn.gitee.io/recorder/");
+	appREADME=MDAbsImg(appREADME,"https://xiangyuecn.gitee.io/recorder/app-support-sample/");
+	npmREADME=MDAbsImg(npmREADME,"https://xiangyuecn.gitee.io/recorder/");
+	
+	var npmPackage=fs.readFileSync(npmHome+"/package.json","utf-8");
+	var hashHistory=fs.readFileSync(npmHome+"/hash-history.txt","utf-8");
+	var versionPatch=fs.existsSync(npmHome+"/version.patch.txt")&&fs.readFileSync(npmHome+"/version.patch.txt","utf-8")||"";
+		
+	var sha1Obj=crypto.createHash('sha1');
+	var writeHashFile=function(path,data){
+		fs.writeFileSync(path, data);
+		sha1Obj.update(data);
+	};
+
+	var refsData={"README.Raw":rootREADME,"编辑提醒":"[​](?本文件为动态生成文件,请勿直接编辑,需要编辑请修改npm-home中的README)"};
+	var exp=/\(\?Ref=(.+?)&Start\)([\S\s]+?)\[.*?\]\(\?RefEnd/g,m;
+	while(m=exp.exec(rootREADME)){
+		refsData["README."+m[1]]=m[2].trim();
+	};
+	exp.lastIndex=0;
+	while(m=exp.exec(appREADME)){
+		refsData["RecordApp.README."+m[1]]=m[2].trim();
+	};
+	console.log("Ref已定义项",Object.keys(refsData));
+
+	var exp=/@@Ref (.+?)@@/g;
+	npmREADME=npmREADME.replace(exp,function(s,a){
+		var v=refsData[a];
+		if(!v){
+			throw new Error("npm README中"+s+"不存在");
+		};
+		return v;
+	});
+	npmREADME=npmREADME.replace(/@@Remove Start@@[\S\s]+@@Remove End@@/g,"");
+	writeHashFile(npmFiles+"/README.md",npmREADME);
+	console.log("已生成"+npmFiles+"/README.md");
+
+	var npmVer=0;
+	var npmPatch=0;
+	npmPackage=npmPackage.replace(/"([\d\.]+)123456.9999"/g,function(s,a){
+		var d=new Date();
+		var v=(""+d.getFullYear()).substr(-2);
+		v+=("0"+(d.getMonth()+1)).substr(-2);
+		v+=("0"+d.getDate()).substr(-2);
+		
+		var patch="00";
+		if(versionPatch){
+			var obj=JSON.parse(versionPatch);
+			var day=obj.date.replace(/-/g,"");
+			if(day.length!=8){
+				throw new Error("versionPatch.date无效");
+			};
+			if(day.substr(-6)==v){
+				patch=("0"+obj.patch).substr(-2);
+			};
+		};
+		
+		v='"'+a+v+patch+'"';
+		npmVer=v;
+		npmPatch=patch;
+		return npmVer;
+	});
+	console.log("\x1B[32m%s\x1B[0m","package version:"+npmVer+" patch:"+npmPatch+',如果需要修改patch,请新建version.patch.txt,格式{"date":"2010-01-01","patch":12}patch取值0-99当日有效');
+	fs.writeFileSync(npmFiles+"/package.json",npmPackage);
+	console.log("已生成"+npmFiles+"/package.json");
+	
+	var copyFile=function(src,dist){
+		var byts=fs.readFileSync(src);
+		writeHashFile(dist, byts);
+		console.log("已复制"+dist);
+	};
+	copyFile("../recorder.mp3.min.js",npmFiles+"/recorder.mp3.min.js");
+	copyFile("../recorder.wav.min.js",npmFiles+"/recorder.wav.min.js");
+	copyFile("recorder-core.js",npmSrc+"/recorder-core.js");
+	srcDirs.forEach(function(dir){
+		var files=fs.readdirSync(dir);
+		fs.mkdirSync(npmSrc+"/"+dir);
+		files.forEach(function(file){
+			copyFile(dir+"/"+file,npmSrc+"/"+dir+"/"+file);
+		});
+	});
+	
+	
+	var writeDTS=function(path,val){
+		fs.writeFileSync(npmFiles+path,val);
+		console.log("已生成"+npmFiles+path);
+	};
+	var recDTS='declare let Recorder : any;\nexport default Recorder;';
+	writeDTS("/index.d.ts", recDTS);
+	writeDTS("/recorder.mp3.min.d.ts", recDTS);
+	writeDTS("/recorder.wav.min.d.ts", recDTS);
+	writeDTS("/src/app-support/app.d.ts", 'declare let RecordApp : any;\nexport default RecordApp;');
+	
+	
+	//记录代码是否有变更
+	var sha1=sha1Obj.digest("hex");
+	var hashArr=JSON.parse(hashHistory||"[]");
+	var hasChange=0;
+	if(!hashArr[0]||hashArr[0].sha1!=sha1){
+		hasChange=1;
+		hashArr.splice(0,0,{sha1:sha1,time:new Date().toLocaleString()});
+		hashArr.length=Math.min(hashArr.length,5);
+		fs.writeFileSync(npmHome+"/hash-history.txt",JSON.stringify(hashArr,null,"\t"));
+	};
+
+	var msg="请记得到"+npmFiles+"目录中上传npm包"+(hasChange?",已发生变更":"");
+	console.log("\x1B["+(hasChange?31:32)+"m%s\x1B[0m",msg);
+	
+	console.log("\x1B[33m%s\x1B[0m","处理完成");
+};

+ 475 - 0
src/package-i18n.js

@@ -0,0 +1,475 @@
+/*
+i18n 简版国际化语言支持,nodejs处理代码
+	- 校验源码中 $T 调用正确性
+	- 提取源码中的 i18n key,生成对应的语言支持文件(合并已有的翻译,新文字需手动翻译)
+
+src源码如果要增加新语言,请到 i18n 目录内新建一个语言对应的js空文件,然后重新生成语言包,再打开那个js进行手动翻译
+
+页面如果需要增加新语言,请在下面定义好包含的页面文件,并且在assets/page-i18n中创建相应文件夹和语言对应的js空文件,然后重新生成语言包,再打开那个js进行手动翻译
+
+支持从源码中提取的文本:
+	- 任何 `$T(` 将会被识别为 $T 调用,包括 xx$T(),不可换行,如果调用参数错误会提示
+	- 页面内任何 `reclang=` 将会识别成需要翻译的文本,如:<x reclang="key">中文文本,不允许出现标签,不允许换行</x>
+*/
+var fs = require("fs");
+
+module.exports=function(){
+	var existsKeys={};
+	//处理src源码
+	Run_src(existsKeys);
+	
+	//处理demo页面和相关js文件
+	console.log("\x1B[33m%s\x1B[0m","开始生成assets的i18n语言包...");
+	Run_assets(existsKeys,"index_html",["../index.html"]);
+	Run_assets(existsKeys,"widget_donate",["../assets/zdemo.widget.donate.js"]);
+	Run_assets(existsKeys,"QuickStart_html",["../QuickStart.html"]);
+	Run_assets(existsKeys,"app_index_html",["../app-support-sample/index.html"]);
+	Run_assets(existsKeys,"app_QuickStart_html",["../app-support-sample/QuickStart.html"]);
+	
+	var uniDir="../app-support-sample/demo_UniApp/uni_modules/Recorder-UniCore/";
+	var uniPage="../app-support-sample/demo_UniApp/pages/recTest/";
+	if(global.UniSupportI18nSrcList){ //额外提供组件路径进行处理
+		Run_assets(existsKeys,"Recorder_UniCore",global.UniSupportI18nSrcList,uniDir+"i18n");
+	};
+	
+	//不作使用,仅用于代码检查,这些文件内全部自带翻译
+	var checks=[];
+	fs.readdirSync("../app-support-sample").forEach((file)=>{
+		if(/-config\.js$/i.test(file)) checks.push("../app-support-sample/"+file);
+	});
+	checks.push(uniPage+"page_i18n.vue");
+	checks.push(uniPage+"test_player___.vue");
+	Run_assets(existsKeys,"temp_check",checks);
+};
+
+var Run_src=function(existsKeys){
+	console.log("\x1B[33m%s\x1B[0m","开始生成src的i18n语言包...");
+	
+	//源文件列表
+	var srcFiles=["recorder-core.js"];
+	["engine","extensions","app-support"].forEach((dir)=>{
+		var arr=[];
+		fs.readdirSync(dir).forEach((file)=>{
+			arr.push(dir+"/"+file);
+		});
+		arr.sort();
+		srcFiles=srcFiles.concat(arr);
+	});
+	
+	//读取源码中的 $T 文本内容,校验调用是否正确
+	var srcFileTexts=[];
+	srcFiles.forEach((file)=>{
+		var srcTxt=fs.readFileSync(file,"utf-8");
+		srcFileTexts.push({
+			file:file
+			,texts:extractAndCheckSrcFile(file,srcTxt,existsKeys)
+		});
+	});
+	
+	//读取要生成的文件列表
+	var outFiles=[];
+	fs.readdirSync("i18n").forEach((file)=>{
+		if(file!="en-US.js") outFiles.push("i18n/"+file);
+	});
+	
+	//生成文件
+	var set={
+		descKey:"desc$"
+	};
+	createLangFile(set,"i18n/en-US.js",srcFileTexts,{});
+	var srcLocaleEn=createLangFile(set,"i18n/en-US.js",srcFileTexts,{},true);
+	outFiles.forEach((file)=>{
+		createLangFile(set,file,srcFileTexts,srcLocaleEn);
+	});
+};
+
+
+var Run_assets=function(existsKeys,folder,srcFiles,baseDir){
+	if(!baseDir)baseDir="../assets/page-i18n/"+folder;
+	if(!fs.existsSync(baseDir)){
+		fs.mkdirSync(baseDir);
+		fs.writeFileSync(baseDir+"/en-US.js","");
+	}
+	//读取源码中的 $T 文本内容,校验调用是否正确
+	var srcFileTexts=[];
+	srcFiles.forEach((file)=>{
+		var srcTxt=fs.readFileSync(file,"utf-8");
+		srcFileTexts.push({
+			file:file
+			,texts:extractAndCheckHtmlFile(file,srcTxt,existsKeys)
+		});
+	});
+	
+	//读取要生成的文件列表
+	var outFiles=[];
+	fs.readdirSync(baseDir).forEach((file)=>{
+		if(file!="en-US.js") outFiles.push(baseDir+"/"+file);
+	});
+	
+	//生成文件
+	var set={
+		isPage:true
+		,pageFolder:folder
+		,descKey:"desc-page-"+folder+"$"
+	};
+	createLangFile(set,baseDir+"/en-US.js",srcFileTexts,{});
+	var srcLocaleEn=createLangFile(set,baseDir+"/en-US.js",srcFileTexts,{},true);
+	outFiles.forEach((file)=>{
+		createLangFile(set,file,srcFileTexts,srcLocaleEn);
+	});
+};
+
+
+
+var extractAndCheckHtmlFile=function(file,srcTxt,existsKeys){
+	//html文本中的 reclang 转换成 $T 形式
+	var lines=srcTxt.split("\n");
+	for(var iL=0;iL<lines.length;iL++){
+		var tag=file+" 第"+(iL+1)+"行的reclang:";
+		var tArr=lines[iL].split(/reclang=/);
+		var texts=[]; texts.push(tArr[0]);
+for(var iT=1;iT<tArr.length;iT++){
+	var code=tArr[iT];
+	var m=/^(['"])([^>]+)\1([^>]*)>([^<]*)(<\/\w+>)/.exec(code);
+	if(!m)throw new Error(tag+'格式不匹配 '+code);
+	var key=m[2],txt=m[4];
+	texts.push("reclang="+m[1]+m[2]+m[1]+m[3]+">");
+	texts.push(' $T('+WrapStr(key+"::"+txt)+') ');
+	texts.push(m[5]+code.substr(m[0].length));
+}
+		lines[iL]=texts.join("")
+	}
+	
+	srcTxt=lines.join("\n");
+	return extractAndCheckSrcFile(file,srcTxt,existsKeys);
+};
+var extractAndCheckSrcFile=function(file,srcTxt,existsKeys){
+	var lines=srcTxt.split("\n"),allTexts=[];
+	for(var iL=0;iL<lines.length;iL++){
+		var tag=file+" 第"+(iL+1)+"行的$T:";
+		var tArr=lines[iL].split(/\$T\(/);
+for(var iT=1;iT<tArr.length;iT++){
+	var code=tArr[iT].trim(),rawCode="$T"+"("+code,texts=[];
+	
+	//第一个字符串
+	var m1=/^(['"])([\w\-]+):([\w\-]*):/.exec(code);
+	if(!m1)throw new Error(tag+'不是 $T("key:lang:text" 形式');
+	var key=m1[2],lang=m1[3]||"zh";
+	if(lang!="zh")throw new Error(tag+'第一个字符串lang不是zh');
+	if(existsKeys[key])throw new Error(tag+'key已存在:'+key);
+	existsKeys[key]=1;
+	
+	var tEnd=code.indexOf(m1[1],1);
+	if(tEnd<1)throw new Error(tag+'第一个字符串不在一行闭合');
+	texts.push({txt:code.substring(m1[0].length,tEnd),lang:lang});
+	code=code.substr(tEnd+1).trim();
+	
+	//提取后面的字符串
+	var hasArgs=true,strN=0;
+	while(true){
+		strN++;
+		if(code[0]==")"){
+			hasArgs=false;
+			break; //没有更多文本和参数
+		}else if(!code||code[0]!=","){
+			throw new Error(tag+'第'+strN+'个字符串后面不是 ,或) 结尾');
+		}else{
+			code=code.substr(1).trim();
+			if(/^\d/.test(code)){
+				break; //后面是变量
+			}else if(/^['"]/.test(code)){
+				//后面是字符串
+				var m2=/^(['"])([\w\-]*):/.exec(code);
+				if(!m2)throw new Error(tag+'第'+(strN+1)+'个字符串不是 "lang:text" 格式');
+				var lang=m2[2];
+				if(strN==1){
+					lang=lang||"en";
+					if(lang!="en")console.log("\x1B[33m%s\x1B[0m",tag+'第2个字符串lang不是en');
+				}else if(!lang){
+					throw new Error(tag+'第'+(strN+1)+'个字符串必须提供lang');
+				}
+				
+				var tEnd=code.indexOf(m2[1],1);
+				if(tEnd<1)throw new Error(tag+'第'+(strN+1)+'个字符串不在一行闭合');
+				texts.push({txt:code.substring(m2[0].length,tEnd),lang:lang});
+				code=code.substr(tEnd+1).trim();
+			}else{
+				throw new Error(tag+'第'+strN+'个字符串后面这个参数未知格式');
+			}
+		}
+	}
+	
+	//有变量
+	var argsLen=0;
+	if(hasArgs){
+		while(/\([^\)]*\)/.test(code)){//去掉成对的括号
+			code=code.replace(/\([^\)]*\)/," ");
+		}
+		var cEnd=code.indexOf(")");
+		if(cEnd<1)throw new Error(tag+"函数调用未闭合");
+		
+		code=code.substring(0,cEnd).trim();
+		var arr=code.split(",");
+		if(arr.length==1){//指定了数量,这种只返回key不传args
+			if(+arr[0]+""!==arr[0])throw new Error(tag+"文本后面的参数不是一个数字(只返回key)");
+			if(+arr[0]<1)throw new Error(tag+"文本后面的参数数量值不能小于1(只返回key)");
+			argsLen=+arr[0];
+		}else{
+			if(arr[0]!=="0")throw new Error(tag+"文本后面的参数必须是一个0,0后面再放args");
+			argsLen=arr.length-1;
+		}
+	}
+	//计算文本中的变量是否一致
+	for(var i=0;i<texts.length;i++){
+		var txt=texts[i].txt;
+		var err=CheckTxtArgsLen(txt,argsLen);
+		if(err)throw new Error(tag+err);
+	}
+	
+	//提取完成
+	allTexts.push({
+		key:key,argsLen:argsLen
+		,lineNo:iL+1,rawCode:rawCode
+		,texts:texts
+	});
+}
+	};
+	return allTexts;
+}
+
+
+var CheckTxtArgsLen=function(txt,argsLen){
+	var mps={},nums=[],exp=/\{(\d+)(\!?)\}/g,m;
+	while(m=exp.exec(txt)) mps[m[1]]=1;
+	for(var k in mps)nums.push(+k);
+	nums.sort();
+	if(nums.length!=argsLen)return "参数数量不一致";
+	if(argsLen && argsLen!=nums[nums.length-1])return "参数位置的最大值不一致";
+	for(var j=1;j<nums.length;j++){
+		if(nums[j]-1!=nums[j-1]) return "字符串中参数位置不连续";
+	}
+	return "";
+};
+var WrapStr=function(str){
+	str=(str==null?"":str)+"";
+	str=str.replace(/\\/g,"\\\\");
+	str=str.replace(/\n/g,"\\n");
+	if(/"/.test(str)){
+		if(str.indexOf("'")+1) throw new Error("WrapStr ': "+str);
+		return "'"+str+"'"
+	}
+	if(str.indexOf("\"")+1) throw new Error("WrapStr \": "+str);
+	return '"'+str+'"';
+}
+
+var createLangFile=function(config,file,srcFileTexts,srcLocaleEn,readLocale){
+	var tag="生成文件["+file+"]:";
+	var PageHide=config.isPage?"//@@Exec ":"";
+	
+	var oldTxt=fs.readFileSync(file,"utf-8"),oldCode=oldTxt,oldTxt1="",oldTxt2="";
+	var m=/\/\/@@User Code-1 Begin.+?@@([\S\s]+)\/\/@@User Code-1 End.+?@@/.exec(oldTxt);
+	if(m){
+		oldCode=oldTxt.substring(0,m.index);
+		oldCode+=new Array(m[1].split("\n").length).join("\n");//保持行数
+		oldCode+=oldTxt.substring(m.index+m[0].length);
+		oldTxt=oldCode;
+		oldTxt1=m[1].trim();
+	}
+	var m=/\/\/@@User Code-2 Begin.+?@@([\S\s]+)\/\/@@User Code-2 End.+?@@/.exec(oldTxt);
+	if(m){
+		oldCode=oldTxt.substring(0,m.index);
+		oldCode+=new Array(m[1].split("\n").length).join("\n");
+		oldCode+=oldTxt.substring(m.index+m[0].length);
+		oldTxt=oldCode;
+		oldTxt2=m[1].trim();
+	}
+	
+	//提供临时环境,提取出老的数据
+	var Recorder={CLog:function(){}},localeCur={},localeZh={},localeOk={};
+	Object.Recorder=Recorder;
+	var i18n=Recorder.i18n={
+		lang:(/([^\/\\]+)\.js$/i.exec(file)||[])[1]||""
+		,alias:{},data:{}
+		,put:function(set,texts){
+			i18n.lang=set.lang;
+			for(var i=0;i<texts.length;i++){
+				var v=texts[i],zhTxt="",okTxt="";
+				if(!v && i==0)continue;
+				if(v===9){
+					i++; v=texts[i];
+					zhTxt=v;
+					i++; v=texts[i];
+				}
+				if(v===8){
+					i++; v=texts[i];
+					i++; v=texts[i];
+				}
+				if(v===7){
+					i++; v=texts[i];
+					okTxt=v;
+					i++; v=texts[i];
+				}
+				
+				var m=/^([\w\-]+):/.exec(v);
+				if(!m)throw new Error("无法提取key:"+v);
+				var key=m[1],v=v.substr(key.length+1);
+				localeCur[key]=v;
+				localeZh[key]=zhTxt;
+				localeOk[key]=okTxt;
+			}
+		}
+	};
+	try{
+		oldCode=oldCode.replace(/\/\/@@Exec/g," ");
+		oldCode=oldCode.replace(/\/\/@@PutList/g,"''");
+		oldCode=oldCode.replace(/\/\/@@Put0/g,",");
+		oldCode=oldCode.replace(/\/\/@@zh=/g,",9,");
+		oldCode=oldCode.replace(/\/\/@@en=/g,",8,");
+		oldCode=oldCode.replace(/\/\/@@OK=/g,",7,");
+		oldCode+="\n//@ sourceURL="+file;
+		eval(oldCode); //unsafe?
+	}catch(e){
+		console.log("\x1B[31m%s\x1B[0m",tag+"执行老文件中的内容失败");
+		console.error(e);
+		throw new Error(tag+"执行老文件中的内容失败");
+	};
+	if(readLocale){
+		return localeCur;
+	};
+	
+	var desc=((/(.+?)。/.exec(i18n.data[config.descKey+i18n.lang])||[])[1]||"-").substr(0,50);
+	var langs=[];
+	for(var k in i18n.alias) langs.push('"'+k+'"');
+	langs.push('"'+i18n.lang+'"');
+	
+	//生成新文件内容
+	var codes=[];
+	codes.push(
+`/*
+Recorder ${file}
+https://github.com/xiangyuecn/Recorder
+
+Usage: Recorder.i18n.lang=${langs.join(" or ")}
+
+Desc: ${i18n.data[config.descKey+i18n.lang]||""}
+
+注意:请勿修改//@@打头的文本行;以下代码结构由/src/package-i18n.js自动生成,只允许在字符串中填写翻译后的文本,请勿改变代码结构;翻译的文本如果需要明确的空值,请填写"=Empty";文本中的变量用{n}表示(n代表第几个变量),所有变量必须都出现至少一次,如果不要某变量用{n!}表示
+
+Note: Do not modify the text lines starting with //@@; The following code structure is automatically generated by /src/package-i18n.js, only the translated text is allowed to be filled in the string, please do not change the code structure; If the translated text requires an explicit empty value, please fill in "=Empty"; Variables in the text are represented by {n} (n represents the number of variables), all variables must appear at least once, if a variable is not required, it is represented by {n!}
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	factory(win.Recorder,browser);
+}(function(Recorder,isBrowser){
+"use strict";
+var i18n=Recorder.i18n;
+`);
+	codes.push('//@@User Code-1 Begin 手写代码放这里 Put the handwritten code here @@');
+	codes.push(oldTxt1);
+	codes.push('//@@User Code-1 End @@');
+	
+	codes.push('');
+	codes.push('//@@Exec i18n.lang='+langs[0]+';');
+	if(PageHide){
+		codes.push('Recorder.CLog(\'Import Page['+config.pageFolder+'] lang='+langs[0]+'\');');
+	}else{
+		codes.push('Recorder.CLog(\'Import Recorder i18n lang='+langs[0]+'\');');
+	}
+	
+	codes.push('');
+	var hasAlias=0;
+	for(var k in i18n.alias){
+		hasAlias=1;
+		codes.push(PageHide+'i18n.alias["'+k+'"]="'+i18n.alias[k]+'";');
+	};
+	if(!hasAlias){
+		codes.push((PageHide||"")+'//i18n.alias["other-lang-key"]="'+i18n.lang+'";');
+	};
+	
+	codes.push('');
+	codes.push('var putSet={lang:"'+i18n.lang+'"};');
+	
+	codes.push('');
+	codes.push(PageHide+'i18n.data["rtl$'+i18n.lang+'"]='+(!!i18n.data["rtl$"+i18n.lang])+';');
+	codes.push('i18n.data["'+config.descKey+i18n.lang+'"]='+WrapStr(i18n.data[config.descKey+i18n.lang]||"")+';');
+	codes.push('//@@Exec i18n.GenerateDisplayEnglish='+(i18n.GenerateDisplayEnglish?"true":"false")+';');
+	
+	var iF=0,totalCount=0,emptyCount=0,clearCount=0;
+	if(file.indexOf("zh-CN.js")+1){ //中文无需翻译
+		iF=srcFileTexts.length;
+		codes.push('//@@Exec i18n.put(putSet,[]);');
+	};
+	for(;iF<srcFileTexts.length;iF++){
+		var srcFile=srcFileTexts[iF].file;
+		var allTexts=srcFileTexts[iF].texts;
+		if(!allTexts.length)continue;
+		
+		codes.push('');
+		codes.push('');
+		codes.push('');
+		codes.push('//*************** Begin srcFile='+srcFile+' ***************');
+		codes.push('i18n.put(putSet,');
+		codes.push('[ //@@PutList ');
+		for(var tF=0;tF<allTexts.length;tF++){
+			//源文件中的文本数据
+			var keyItem=allTexts[tF],key=keyItem.key,argsLen=keyItem.argsLen;
+			var texts=keyItem.texts,zhTxt="",okTxt="";
+			for(var i=0;i<texts.length;i++){
+				var o=texts[i];
+				if(o.lang=="zh")zhTxt=o.txt;
+				if(o.lang==i18n.lang)okTxt=o.txt;
+			}
+			//源文件数据保持到语言文件中
+			codes.push('');
+			codes.push('//@@zh='+WrapStr(zhTxt));
+			if(i18n.GenerateDisplayEnglish){
+				codes.push('//@@en='+WrapStr(srcLocaleEn[key]||""));
+			}
+			if(okTxt){
+				codes.push('//@@OK='+WrapStr(okTxt));
+			}
+			//从老文件中提取出有效的翻译结果
+			var curTxt="";
+			if(localeCur[key] && localeZh[key]==zhTxt){//有老结果,且新老源文字相同
+				if(!localeOk[key] || localeOk[key]==okTxt){//有老源的翻译,新源未变更,如果新的变更了就直接丢弃老的
+					curTxt=localeCur[key];
+				}
+			}
+			if(curTxt){//校验参数个数是否正确
+				var err=CheckTxtArgsLen(curTxt,argsLen);
+				if(err)curTxt="";
+			}
+			if(!curTxt && localeCur[key]) clearCount++;
+			curTxt=curTxt||okTxt;
+			if(!curTxt) emptyCount++;
+			totalCount++;
+			
+			if(!tF)codes.push('//@@Put0');
+			codes.push(
+				(tF?',':' ')
+				+('"'+key+':"+ //'+(argsLen?"args: {1}"+(argsLen>1?"-{"+argsLen+"}":""):"no args")+'\n')
+				+"       "+WrapStr(curTxt)
+				+(curTxt?"":" /** TODO: translate to "+i18n.lang+" **/")
+			);
+		}
+		codes.push('');
+		codes.push(']);');
+		codes.push('//*************** End srcFile='+srcFile+' ***************');
+	}
+	
+	codes.push('');
+	codes.push('//@@User Code-2 Begin 手写代码放这里 Put the handwritten code here @@');
+	codes.push(oldTxt2);
+	codes.push('//@@User Code-2 End @@');
+	codes.push('');
+	codes.push('}));');
+	
+	fs.writeFileSync(file,codes.join("\n"));
+	if(/Template.js$/.test(file))emptyCount=0;
+	console.log("已生成:"+file
+		+(emptyCount?",\x1B[31m有"+emptyCount+"条未翻译\x1B[0m":",OK")
+		+(clearCount?",有"+clearCount+"条因变更重置":"")
+		+"      "+totalCount+" | "+i18n.lang+" |"+desc);
+};
+

+ 29 - 0
src/package-lock.json

@@ -0,0 +1,29 @@
+{
+  "name": "build",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "uglify-js": {
+      "version": "3.3.16",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.3.16.tgz",
+      "integrity": "sha512-FMh5SRqJRGhv9BbaTffENIpDDQIoPDR8DBraunGORGhySArsXlw9++CN+BWzPBLpoI4RcSnpfGPnilTxWL3Vvg==",
+      "requires": {
+        "commander": "~2.15.0",
+        "source-map": "~0.6.1"
+      },
+      "dependencies": {
+        "commander": {
+          "version": "2.15.1",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
+          "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag=="
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        }
+      }
+    }
+  }
+}

+ 10 - 0
src/package.json

@@ -0,0 +1,10 @@
+{
+  "name": "build",
+  "version": "1.0.0",
+  "scripts": {
+    "start": "node package-build.js"
+  },
+  "dependencies": {
+    "uglify-js": "3.3.16"
+  }
+}

+ 1849 - 0
src/recorder-core.js

@@ -0,0 +1,1849 @@
+/*
+录音
+https://github.com/xiangyuecn/Recorder
+*/
+(function(factory){
+	var browser=typeof window=="object" && !!window.document;
+	var win=browser?window:Object; //非浏览器环境,Recorder挂载在Object下面
+	factory(win,browser);
+	//umd returnExports.js
+	if(typeof(define)=='function' && define.amd){
+		define(function(){
+			return win.Recorder;
+		});
+	};
+	if(typeof(module)=='object' && module.exports){
+		module.exports=win.Recorder;
+	};
+}(function(Export,isBrowser){
+"use strict";
+
+var NOOP=function(){};
+var IsNum=function(v){return typeof v=="number"};
+
+var Recorder=function(set){
+	return new initFn(set);
+};
+var LM=Recorder.LM="2024-04-09 19:15";
+var GitUrl="https://github.com/xiangyuecn/Recorder";
+var RecTxt="Recorder";
+var getUserMediaTxt="getUserMedia";
+var srcSampleRateTxt="srcSampleRate";
+var sampleRateTxt="sampleRate";
+var bitRateTxt="bitRate";
+var CatchTxt="catch";
+
+var WRec2=Export[RecTxt];//重复加载js
+if(WRec2&&WRec2.LM==LM){
+	WRec2.CLog(WRec2.i18n.$T("K8zP::重复导入{1}",0,RecTxt),3);
+	return;
+};
+
+
+//是否已经打开了全局的麦克风录音,所有工作都已经准备好了,就等接收音频数据了
+Recorder.IsOpen=function(){
+	var stream=Recorder.Stream;
+	if(stream){
+		var tracks=stream.getTracks&&stream.getTracks()||stream.audioTracks||[];
+		var track=tracks[0];
+		if(track){
+			var state=track.readyState;
+			return state=="live"||state==track.LIVE;
+		};
+	};
+	return false;
+};
+/*H5录音时的AudioContext缓冲大小。会影响H5录音时的onProcess调用速率,相对于AudioContext.sampleRate=48000时,4096接近12帧/s,调节此参数可生成比较流畅的回调动画。
+	取值256, 512, 1024, 2048, 4096, 8192, or 16384
+	注意,取值不能过低,2048开始不同浏览器可能回调速率跟不上造成音质问题。
+	一般无需调整,调整后需要先close掉已打开的录音,再open时才会生效。
+*/
+Recorder.BufferSize=4096;
+//销毁已持有的所有全局资源,当要彻底移除Recorder时需要显式的调用此方法
+Recorder.Destroy=function(){
+	CLog(RecTxt+" Destroy");
+	Disconnect();//断开可能存在的全局Stream、资源
+	
+	for(var k in DestroyList){
+		DestroyList[k]();
+	};
+};
+var DestroyList={};
+//登记一个需要销毁全局资源的处理方法
+Recorder.BindDestroy=function(key,call){
+	DestroyList[key]=call;
+};
+//判断浏览器是否支持录音,随时可以调用。注意:仅仅是检测浏览器支持情况,不会判断和调起用户授权,不会判断是否支持特定格式录音。
+Recorder.Support=function(){
+	if(!isBrowser) return false;
+	var scope=navigator.mediaDevices||{};
+	if(!scope[getUserMediaTxt]){
+		scope=navigator;
+		scope[getUserMediaTxt]||(scope[getUserMediaTxt]=scope.webkitGetUserMedia||scope.mozGetUserMedia||scope.msGetUserMedia);
+	};
+	if(!scope[getUserMediaTxt]){
+		return false;
+	};
+	Recorder.Scope=scope;
+	
+	if(!Recorder.GetContext()){
+		return false;
+	};
+	return true;
+};
+//获取全局的AudioContext对象,如果浏览器不支持将返回null。tryNew时尝试创建新的非全局对象并返回,失败时依旧返回全局的;成功时返回新的,注意用完必须自己调用CloseNewCtx(ctx)关闭。注意:非用户操作(触摸、点击等)时调用返回的ctx.state可能是suspended状态,需要在用户操作时调用ctx.resume恢复成running状态,参考rec的runningContext配置
+Recorder.GetContext=function(tryNew){
+	if(!isBrowser) return null;
+	var AC=window.AudioContext;
+	if(!AC){
+		AC=window.webkitAudioContext;
+	};
+	if(!AC){
+		return null;
+	};
+	
+	var ctx=Recorder.Ctx;
+	if(!ctx||ctx.state=="closed"){
+		//不能反复构造,低版本number of hardware contexts reached maximum (6)
+		ctx=Recorder.Ctx=new AC();
+		Recorder.NewCtxs=Recorder.NewCtxs||[];
+		
+		Recorder.BindDestroy("Ctx",function(){
+			var ctx=Recorder.Ctx;
+			if(ctx&&ctx.close){//能关掉就关掉,关不掉就保留着
+				CloseCtx(ctx);
+				Recorder.Ctx=0;
+			};
+			var arr=Recorder.NewCtxs; Recorder.NewCtxs=[];
+			for(var i=0;i<arr.length;i++)CloseCtx(arr[i]);
+		});
+	};
+	if(tryNew && ctx.close){//没法关闭的不允许再创建
+		try{
+			ctx=new AC();
+			Recorder.NewCtxs.push(ctx);
+		}catch(e){
+			CLog("GetContext tryNew Error",1,e);
+		}
+	};
+	return ctx;
+};
+//关闭新创建的AudioContext,如果是全局的不关闭
+Recorder.CloseNewCtx=function(ctx){
+	if(ctx && ctx!=Recorder.Ctx){
+		CloseCtx(ctx);
+		var arr=Recorder.NewCtxs||[],L=arr.length;
+		for(var i=0;i<arr.length;i++){
+			if(arr[i]==ctx){ arr.splice(i,1); break; }
+		}
+		CLog($T("mSxV::剩{1}个GetContext未close",0,L+"-1="+arr.length),arr.length?3:0);
+	}
+};
+var CloseCtx=function(ctx){
+	if(ctx && ctx.close){
+		ctx._isC=1;
+		try{ ctx.close() }catch(e){ CLog("ctx close err",1,e) }
+	}
+};
+//当AudioContext的状态是suspended时,调用resume恢复状态,但如果没有用户操作resume可能没有回调,封装解决此回调问题;check(count)返回true继续尝试resume,返回false终止任务(不回调False)
+var ResumeCtx=Recorder.ResumeCtx=function(ctx,check,True,False){
+	var isEnd=0,isBind=0,isLsSC=0,runC=0,EL="EventListener",Tag="ResumeCtx ";
+	var end=function(err,ok){
+		if(isBind){ bind() }
+		if(!isEnd){ isEnd=1; //回调结果
+			err&&False(err,runC);
+			ok&&True(runC);
+		}
+		if(ok){ //监听后续状态变化
+			if(!ctx._LsSC && ctx["add"+EL]) ctx["add"+EL]("statechange",run);
+			ctx._LsSC=1; isLsSC=1;
+		}
+	};
+	var bind=function(add){
+		if(add && isBind) return; isBind=add?1:0;
+		var types=["focus","mousedown","mouseup","touchstart","touchend"];
+		for(var i=0;i<types.length;i++)
+			window[(add?"add":"remove")+EL](types[i],run,true);
+	};
+	var run=function(){
+		var sVal=ctx.state,spEnd=CtxSpEnd(sVal);
+		if(!isEnd && !check(spEnd?++runC:runC))return end(); //终止,不回调
+		if(spEnd){
+			if(isLsSC)CLog(Tag+"sc "+sVal,3);
+			bind(1); //绑定用户事件尝试恢复
+			ctx.resume().then(function(){ //resume回调不可靠
+				if(isLsSC)CLog(Tag+"sc "+ctx.state);
+				end(0,1);
+			})[CatchTxt](function(e){ //出错且无法恢复
+				CLog(Tag+"error",1,e);
+				if(!CtxSpEnd(ctx.state)){
+					end(e.message||"error");
+				}
+			});
+		}else if(sVal=="closed"){
+			if(isLsSC && !ctx._isC)CLog(Tag+"sc "+sVal,1); //无法恢复,打个日志
+			end("ctx closed");
+		}else{ end(0,1) }; //running 或老的无state
+	};
+	run();
+};
+var CtxSpEnd=Recorder.CtxSpEnd=function(v){
+	return v=="suspended"||v=="interrupted"; //后面这个仅iOS有
+};
+var CtxState=function(ctx){
+	var v=ctx.state,msg="ctx.state="+v;
+	if(CtxSpEnd(v))msg+=$T("nMIy::(注意:ctx不是running状态,rec.open和start至少要有一个在用户操作(触摸、点击等)时进行调用,否则将在rec.start时尝试进行ctx.resume,可能会产生兼容性问题(仅iOS),请参阅文档中runningContext配置)");
+	return msg;
+};
+
+
+/*是否启用MediaRecorder.WebM.PCM来进行音频采集连接(如果浏览器支持的话),默认启用,禁用或者不支持时将使用AudioWorklet或ScriptProcessor来连接;MediaRecorder采集到的音频数据比其他方式更好,几乎不存在丢帧现象,所以音质明显会好很多,建议保持开启*/
+var ConnectEnableWebM="ConnectEnableWebM";
+Recorder[ConnectEnableWebM]=true;
+
+/*是否启用AudioWorklet特性来进行音频采集连接(如果浏览器支持的话),默认禁用,禁用或不支持时将使用过时的ScriptProcessor来连接(如果方法还在的话),当前AudioWorklet的实现在移动端没有ScriptProcessor稳健;ConnectEnableWebM如果启用并且有效时,本参数将不起作用*/
+var ConnectEnableWorklet="ConnectEnableWorklet";
+Recorder[ConnectEnableWorklet]=false;
+
+/*初始化H5音频采集连接。如果自行提供了sourceStream将只进行一次简单的连接处理。如果是普通麦克风录音,此时的Stream是全局的,Safari上断开后就无法再次进行连接使用,表现为静音,因此使用全部使用全局处理避免调用到disconnect;全局处理也有利于屏蔽底层细节,start时无需再调用底层接口,提升兼容、可靠性。*/
+var Connect=function(streamStore,isUserMedia){
+	var bufferSize=streamStore.BufferSize||Recorder.BufferSize;
+	
+	var stream=streamStore.Stream;
+	var ctx=stream._RC || stream._c || Recorder.GetContext(true);//2023-06 尽量创建新的ctx,免得Safari再次连接无回调
+	stream._c=ctx;
+	
+	var mediaConn=function(node){
+		var media=stream._m=ctx.createMediaStreamSource(stream);
+		var ctxDest=ctx.destination,cmsdTxt="createMediaStreamDestination";
+		if(ctx[cmsdTxt]){
+			ctxDest=stream._d=ctx[cmsdTxt]();
+		};
+		media.connect(node);
+		node.connect(ctxDest);
+	}
+	var isWebM,isWorklet,badInt,webMTips="";
+	var calls=stream._call;
+	
+	//浏览器回传的音频数据处理
+	var onReceive=function(float32Arr){
+		for(var k0 in calls){//has item
+			var size=float32Arr.length;
+			
+			var pcm=new Int16Array(size);
+			var sum=0;
+			for(var j=0;j<size;j++){//floatTo16BitPCM 
+				var s=Math.max(-1,Math.min(1,float32Arr[j]));
+				s=s<0?s*0x8000:s*0x7FFF;
+				pcm[j]=s;
+				sum+=Math.abs(s);
+			};
+			
+			for(var k in calls){
+				calls[k](pcm,sum);
+			};
+			
+			return;
+		};
+	};
+	
+	var scriptProcessor="ScriptProcessor";//一堆字符串名字,有利于压缩js
+	var audioWorklet="audioWorklet";
+	var recAudioWorklet=RecTxt+" "+audioWorklet;
+	var RecProc="RecProc";
+	var MediaRecorderTxt="MediaRecorder";
+	var MRWebMPCM=MediaRecorderTxt+".WebM.PCM";
+
+
+//===================连接方式三=========================
+	//古董级别的 ScriptProcessor 处理,目前所有浏览器均兼容,虽然是过时的方法,但更稳健,移动端性能比AudioWorklet强
+	var oldFn=ctx.createScriptProcessor||ctx.createJavaScriptNode;
+	var oldIsBest=$T("ZGlf::。由于{1}内部1秒375次回调,在移动端可能会有性能问题导致回调丢失录音变短,PC端无影响,暂不建议开启{1}。",0,audioWorklet);
+	var oldScript=function(){
+		isWorklet=stream.isWorklet=false;
+		_Disconn_n(stream);
+		CLog($T("7TU0::Connect采用老的{1},",0,scriptProcessor)
+			+i18n.get(Recorder[ConnectEnableWorklet]?
+				$T("JwCL::但已设置{1}尝试启用{2}",2)
+				:$T("VGjB::可设置{1}尝试启用{2}",2)
+				,[RecTxt+"."+ConnectEnableWorklet+"=true",audioWorklet]
+			)+webMTips+oldIsBest,3);
+		
+		var process=stream._p=oldFn.call(ctx,bufferSize,1,1);//单声道,省的数据处理复杂
+		mediaConn(process);
+		
+		process.onaudioprocess=function(e){
+			var arr=e.inputBuffer.getChannelData(0);
+			onReceive(arr);
+		};
+	};
+
+
+//===================连接方式二=========================
+var connWorklet=function(){
+	//尝试开启AudioWorklet处理
+	isWebM=stream.isWebM=false;
+	_Disconn_r(stream);
+	
+	isWorklet=stream.isWorklet=!oldFn || Recorder[ConnectEnableWorklet];
+	var AwNode=window.AudioWorkletNode;
+	if(!(isWorklet && ctx[audioWorklet] && AwNode)){
+		oldScript();//被禁用 或 不支持,直接使用老的
+		return;
+	};
+	var clazzUrl=function(){
+		var xf=function(f){return f.toString().replace(/^function|DEL_/g,"").replace(/\$RA/g,recAudioWorklet)};
+		var clazz='class '+RecProc+' extends AudioWorkletProcessor{';
+			clazz+="constructor "+xf(function(option){
+				DEL_super(option);
+				var This=this,bufferSize=option.processorOptions.bufferSize;
+				This.bufferSize=bufferSize;
+				This.buffer=new Float32Array(bufferSize*2);//乱给size搞乱缓冲区不管
+				This.pos=0;
+				This.port.onmessage=function(e){
+					if(e.data.kill){
+						This.kill=true;
+						$C.log("$RA kill call");
+					}
+				};
+				$C.log("$RA .ctor call", option);
+			});
+			
+			//https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor/process 每次回调128个采样数据,1秒375次回调,高频导致移动端性能问题,结果就是回调次数缺斤少两,进而导致丢失数据,PC端似乎没有性能问题
+			clazz+="process "+xf(function(input,b,c){//需要等到ctx激活后才会有回调
+				var This=this,bufferSize=This.bufferSize;
+				var buffer=This.buffer,pos=This.pos;
+				input=(input[0]||[])[0]||[];
+				if(input.length){
+					buffer.set(input,pos);
+					pos+=input.length;
+					
+					var len=~~(pos/bufferSize)*bufferSize;
+					if(len){
+						this.port.postMessage({ val: buffer.slice(0,len) });
+						
+						var more=buffer.subarray(len,pos);
+						buffer=new Float32Array(bufferSize*2);
+						buffer.set(more);
+						pos=more.length;
+						This.buffer=buffer;
+					}
+					This.pos=pos;
+				}
+				return !This.kill;
+			});
+		clazz+='}'
+			+'try{'
+				+'registerProcessor("'+RecProc+'", '+RecProc+')'
+			+'}catch(e){$C.error("'+recAudioWorklet+' Reg Error",e)}';
+		clazz=clazz.replace(/\$C\./g,"console.");//一些编译器会文本替换日志函数
+		//URL.createObjectURL 本地有些浏览器会报 Not allowed to load local resource,直接用dataurl
+		return "data:text/javascript;base64,"+btoa(unescape(encodeURIComponent(clazz)));
+	};
+	
+	var awNext=function(){//可以继续,没有调用断开
+		return isWorklet && stream._na;
+	};
+	var nodeAlive=stream._na=function(){
+		//start时会调用,只要没有收到数据就断定AudioWorklet有问题,恢复用老的
+		if(badInt!==""){//没有回调过数据
+			clearTimeout(badInt);
+			badInt=setTimeout(function(){
+				badInt=0;
+				if(awNext()){
+					CLog($T("MxX1::{1}未返回任何音频,恢复使用{2}",0,audioWorklet,scriptProcessor),3);
+					oldFn&&oldScript();//未来没有老的,可能是误判
+				};
+			},500);
+		};
+	};
+	var createNode=function(){
+		if(!awNext())return;
+		var node=stream._n=new AwNode(ctx, RecProc, {
+			processorOptions:{bufferSize:bufferSize}
+		});
+		mediaConn(node);
+		node.port.onmessage=function(e){
+			if(badInt){
+				clearTimeout(badInt);badInt="";
+			};
+			if(awNext()){
+				onReceive(e.data.val);
+			}else if(!isWorklet){
+				CLog($T("XUap::{1}多余回调",0,audioWorklet),3);
+			};
+		};
+		CLog($T("yOta::Connect采用{1},设置{2}可恢复老式{3}",0,audioWorklet,RecTxt+"."+ConnectEnableWorklet+"=false",scriptProcessor)+webMTips+oldIsBest,3);
+	};
+	
+	//如果start时的resume和下面的构造node同时进行,将会导致部分浏览器崩溃 (STATUS_ACCESS_VIOLATION),源码assets中 ztest_chrome_bug_AudioWorkletNode.html 可测试。所以,将所有代码套到resume里面(不管catch),避免出现这个问题
+	var ctxOK=function(){
+		if(!awNext())return;
+		if(ctx[RecProc]){
+			createNode();
+			return;
+		};
+		var url=clazzUrl();
+		ctx[audioWorklet].addModule(url).then(function(e){
+			if(!awNext())return;
+			ctx[RecProc]=1;
+			createNode();
+			if(badInt){//重新计时
+				nodeAlive();
+			};
+		})[CatchTxt](function(e){ //fix 关键字,保证catch压缩时保持字符串形式
+			CLog(audioWorklet+".addModule Error",1,e);
+			awNext()&&oldScript();
+		});
+	};
+	ResumeCtx(ctx,function(){ return awNext() } ,ctxOK,ctxOK);
+};
+
+
+//===================连接方式一=========================
+var connWebM=function(){
+	//尝试开启MediaRecorder录制webm+pcm处理
+	var MR=window[MediaRecorderTxt];
+	var onData="ondataavailable";
+	var webmType="audio/webm; codecs=pcm";
+	isWebM=stream.isWebM=Recorder[ConnectEnableWebM];
+	
+	var supportMR=MR && (onData in MR.prototype) && MR.isTypeSupported(webmType);
+	webMTips=supportMR?"":$T("VwPd::(此浏览器不支持{1})",0,MRWebMPCM);
+	if(!isUserMedia || !isWebM || !supportMR){
+		connWorklet(); //非麦克风录音(MediaRecorder采样率不可控) 或 被禁用 或 不支持MediaRecorder 或 不支持webm+pcm
+		return;
+	}
+	
+	var mrNext=function(){//可以继续,没有调用断开
+		return isWebM && stream._ra;
+	};
+	var mrAlive=stream._ra=function(){
+		//start时会调用,只要没有收到数据就断定MediaRecorder有问题,降级处理
+		if(badInt!==""){//没有回调过数据
+			clearTimeout(badInt);
+			badInt=setTimeout(function(){
+				//badInt=0; 保留给nodeAlive继续判断
+				if(mrNext()){
+					CLog($T("vHnb::{1}未返回任何音频,降级使用{2}",0,MediaRecorderTxt,audioWorklet),3);
+					connWorklet();
+				};
+			},500);
+		};
+	};
+	
+	var mrSet=Object.assign({mimeType:webmType}, Recorder.ConnectWebMOptions);
+	var mr=stream._r=new MR(stream, mrSet);
+	var webmData=stream._rd={sampleRate:ctx[sampleRateTxt]};
+	mr[onData]=function(e){
+		//提取webm中的pcm数据,提取失败就等着badInt超时降级处理
+		var reader=new FileReader();
+		reader.onloadend=function(){
+			if(mrNext()){
+				var f32arr=WebM_Extract(new Uint8Array(reader.result),webmData);
+				if(!f32arr)return;
+				if(f32arr==-1){//无法提取,立即降级
+					connWorklet();
+					return;
+				};
+				
+				if(badInt){
+					clearTimeout(badInt);badInt="";
+				};
+				onReceive(f32arr);
+			}else if(!isWebM){
+				CLog($T("O9P7::{1}多余回调",0,MediaRecorderTxt),3);
+			};
+		};
+		reader.readAsArrayBuffer(e.data);
+	};
+	mr.start(~~(bufferSize/48));//按48k时的回调间隔
+	CLog($T("LMEm::Connect采用{1},设置{2}可恢复使用{3}或老式{4}",0,MRWebMPCM,RecTxt+"."+ConnectEnableWebM+"=false",audioWorklet,scriptProcessor));
+};
+
+	connWebM();
+};
+var ConnAlive=function(stream){
+	if(stream._na) stream._na(); //检查AudioWorklet连接是否有效,无效就回滚到老的ScriptProcessor
+	if(stream._ra) stream._ra(); //检查MediaRecorder连接是否有效,无效就降级处理
+};
+var _Disconn_n=function(stream){
+	stream._na=null;
+	if(stream._n){
+		stream._n.port.postMessage({kill:true});
+		stream._n.disconnect();
+		stream._n=null;
+	};
+};
+var _Disconn_r=function(stream){
+	stream._ra=null;
+	if(stream._r){
+		try{ stream._r.stop() }catch(e){ CLog("mr stop err",1,e) }
+		stream._r=null;
+	};
+};
+var Disconnect=function(streamStore){
+	streamStore=streamStore||Recorder;
+	var isGlobal=streamStore==Recorder;
+	
+	var stream=streamStore.Stream;
+	if(stream){
+		if(stream._m){
+			stream._m.disconnect();
+			stream._m=null;
+		};
+		if(!stream._RC && stream._c){//提供的runningContext不处理
+			Recorder.CloseNewCtx(stream._c);
+		};
+		stream._RC=null; stream._c=null;
+		if(stream._d){
+			StopS_(stream._d.stream);
+			stream._d=null;
+		};
+		if(stream._p){
+			stream._p.disconnect();
+			stream._p.onaudioprocess=stream._p=null;
+		};
+		_Disconn_n(stream);
+		_Disconn_r(stream);
+		
+		if(isGlobal){//全局的时候,要把流关掉(麦克风),直接提供的流不处理
+			StopS_(stream);
+		};
+	};
+	streamStore.Stream=0;
+};
+//关闭一个音频流
+var StopS_=Recorder.StopS_=function(stream){
+	var tracks=stream.getTracks&&stream.getTracks()||stream.audioTracks||[];
+	for(var i=0;i<tracks.length;i++){
+		var track=tracks[i];
+		track.stop&&track.stop();
+	};
+	stream.stop&&stream.stop();
+};
+
+/*对pcm数据的采样率进行转换
+pcmDatas: [[Int16,...]] pcm片段列表
+pcmSampleRate:48000 pcm数据的采样率
+newSampleRate:16000 需要转换成的采样率,newSampleRate>=pcmSampleRate时不会进行任何处理,小于时会进行重新采样
+prevChunkInfo:{} 可选,上次调用时的返回值,用于连续转换,本次调用将从上次结束位置开始进行处理。或可自行定义一个ChunkInfo从pcmDatas指定的位置开始进行转换
+option:{ 可选,配置项
+		frameSize:123456 帧大小,每帧的PCM Int16的数量,采样率转换后的pcm长度为frameSize的整数倍,用于连续转换。目前仅在mp3格式时才有用,frameSize取值为1152,这样编码出来的mp3时长和pcm的时长完全一致,否则会因为mp3最后一帧录音不够填满时添加填充数据导致mp3的时长变长。
+		frameType:"" 帧类型,一般为rec.set.type,提供此参数时无需提供frameSize,会自动使用最佳的值给frameSize赋值,目前仅支持mp3=1152(MPEG1 Layer3的每帧采采样数),其他类型=1。
+			以上两个参数用于连续转换时使用,最多使用一个,不提供时不进行帧的特殊处理,提供时必须同时提供prevChunkInfo才有作用。最后一段数据处理时无需提供帧大小以便输出最后一丁点残留数据。
+	}
+
+返回ChunkInfo:{
+	//可定义,从指定位置开始转换到结尾
+	index:0 pcmDatas已处理到的索引
+	offset:0.0 已处理到的index对应的pcm中的偏移的下一个位置
+	
+	//可定义,指定的一个滤波配置:默认使用Recorder.IIRFilter低通滤波(可有效抑制混叠产生的杂音,新采样率大于pcm采样率的75%时不默认滤波),如果提供了配置但fn为null时将不滤波;sr为此滤波函数对应的初始化采样率,当采样率和pcmSampleRate参数不一致时将重新设为默认函数
+	filter:null||{fn:fn(sample),sr:pcmSampleRate}
+	
+	//仅作为返回值
+	frameNext:null||[Int16,...] 下一帧的部分数据,frameSize设置了的时候才可能会有
+	sampleRate:16000 结果的采样率,<=newSampleRate
+	data:[Int16,...] 转换后的PCM结果;如果是连续转换,并且pcmDatas中并没有新数据时,data的长度可能为0
+}
+*/
+Recorder.SampleData=function(pcmDatas,pcmSampleRate,newSampleRate,prevChunkInfo,option){
+	var Txt="SampleData";
+	prevChunkInfo||(prevChunkInfo={});
+	var index=prevChunkInfo.index||0;
+	var offset=prevChunkInfo.offset||0;
+	
+	var filter=prevChunkInfo.filter;
+	if(filter&&filter.fn&&filter.sr!=pcmSampleRate){
+		filter=null; CLog($T("d48C::{1}的filter采样率变了,重设滤波",0,Txt),3);
+	};
+	if(!filter){//采样率差距比较大才开启低通滤波,最高频率用新采样率频率的3/4
+		var freq=newSampleRate>pcmSampleRate*3/4?0: newSampleRate/2 *3/4;
+		filter={fn:freq?Recorder.IIRFilter(true,pcmSampleRate,freq):0};
+	};
+	filter.sr=pcmSampleRate;
+	var filterFn=filter.fn;
+	
+	var frameNext=prevChunkInfo.frameNext||[];
+	option||(option={});
+	var frameSize=option.frameSize||1;
+	if(option.frameType){
+		frameSize=option.frameType=="mp3"?1152:1;
+	};
+	
+	var nLen=pcmDatas.length;
+	if(index>nLen+1){
+		CLog($T("tlbC::{1}似乎传入了未重置chunk {2}",0,Txt,index+">"+nLen),3);
+	};
+	var size=0;
+	for(var i=index;i<nLen;i++){
+		size+=pcmDatas[i].length;
+	};
+	size=Math.max(0,size-Math.floor(offset));
+	
+	//采样 https://www.cnblogs.com/blqw/p/3782420.html
+	var step=pcmSampleRate/newSampleRate;
+	if(step>1){//新采样低于录音采样,进行抽样
+		size=Math.floor(size/step);
+	}else{//新采样高于录音采样不处理,省去了插值处理
+		step=1;
+		newSampleRate=pcmSampleRate;
+	};
+	
+	size+=frameNext.length;
+	var res=new Int16Array(size);
+	var idx=0;
+	//添加上一次不够一帧的剩余数据
+	for(var i=0;i<frameNext.length;i++){
+		res[idx]=frameNext[i];
+		idx++;
+	};
+	//处理数据
+	for (;index<nLen;index++) {
+		var o=pcmDatas[index];
+		var i=offset,il=o.length;
+		var F=filterFn&&filterFn.Embed,F1=0,F2=0,Fx=0,Fy=0;//低通滤波后的数据
+		for(var i0=0,i2=0;i0<il;i0++,i2++){
+			if(i2<il){
+				if(F){//IIRFilter代码内置,比函数调用快4倍
+					Fx=o[i2];
+					Fy=F.b0 * Fx + F.b1 * F.x1 + F.b0 * F.x2 - F.a1 * F.y1 - F.a2 * F.y2;
+					F.x2 = F.x1; F.x1 = Fx; F.y2 = F.y1; F.y1 = Fy;
+				}else{ Fy=filterFn?filterFn(o[i2]):o[i2]; }
+			}
+			F1=F2; F2=Fy;
+			if(i2==0){ i0--; continue; } //首次只计算o[0]
+			//res[idx]=o[Math.round(i)]; 直接简单抽样
+			
+			//https://www.cnblogs.com/xiaoqi/p/6993912.html
+			//当前点=当前点+到后面一个点之间的增量,音质比直接简单抽样好些
+			var before = Math.floor(i);
+			if(i0!=before)continue;
+			var after = Math.ceil(i);
+			var atPoint = i - before;
+			
+			var beforeVal=F1;
+			var afterVal=after<il ? F2 : beforeVal; //后个点越界了,忽略不计
+			var val=beforeVal+(afterVal-beforeVal)*atPoint;
+			
+			if(val>0x7FFF) val=0x7FFF; else if(val<-0x8000) val=-0x8000; //Int16越界处理
+			res[idx]=val;
+			
+			idx++;
+			i+=step;//抽样
+		};
+		offset=Math.max(0, i-il); //不太可能出现负数
+	};
+	//帧处理
+	frameNext=null;
+	var frameNextSize=res.length%frameSize;
+	if(frameNextSize>0){
+		var u8Pos=(res.length-frameNextSize)*2;
+		frameNext=new Int16Array(res.buffer.slice(u8Pos));
+		res=new Int16Array(res.buffer.slice(0,u8Pos));
+	};
+	
+	return {
+		index:index
+		,offset:offset
+		,filter:filter
+		
+		,frameNext:frameNext
+		,sampleRate:newSampleRate
+		,data:res
+	};
+};
+
+/*IIR低通、高通滤波,移植自:https://gitee.com/52jian/digital-audio-filter AudioFilter.java
+	useLowPass: true或false,true为低通滤波,false为高通滤波
+	sampleRate: 待处理pcm的采样率
+	freq: 截止频率Hz,最大频率为sampleRate/2,低通时会切掉高于此频率的声音,高通时会切掉低于此频率的声音,注意滤波并非100%的切掉不需要的声音,而是减弱频率对应的声音,离截止频率越远对应声音减弱越厉害,离截止频率越近声音就几乎无衰减
+	返回的是一个函数,用此函数对pcm的每个采样值按顺序进行处理即可(不同pcm不可共用);注意此函数返回值可能会越界超过Int16范围,自行限制一下即可:Math.min(Math.max(val,-0x8000),0x7FFF)
+可重新赋值一个函数,来改变Recorder的默认行为,比如SampleData中的低通滤波*/
+Recorder.IIRFilter=function(useLowPass, sampleRate, freq){
+	var ov = 2 * Math.PI * freq / sampleRate;
+	var sn = Math.sin(ov);
+	var cs = Math.cos(ov);
+	var alpha = sn / 2;
+	
+	var a0 = 1 + alpha;
+	var a1 = (-2 * cs) / a0;
+	var a2 = (1 - alpha) / a0;
+	if(useLowPass){
+		var b0 = (1 - cs) / 2 / a0;
+		var b1 = (1 - cs) / a0;
+	}else{
+		var b0 = (1 + cs) / 2 / a0;
+		var b1 = -(1 + cs) / a0;
+	}
+	
+	var x1=0,x2=0,y=0,y1=0,y2=0;
+	var fn=function(x){
+		y = b0 * x + b1 * x1 + b0 * x2 - a1 * y1 - a2 * y2;
+		x2 = x1; x1 = x;
+		y2 = y1; y1 = y;
+		return y;
+	};
+	fn.Embed={x1:0,x2:0,y1:0,y2:0,b0:b0,b1:b1,a1:a1,a2:a2};
+	return fn;
+};
+
+
+/*计算音量百分比的一个方法
+pcmAbsSum: pcm Int16所有采样的绝对值的和
+pcmLength: pcm长度
+返回值:0-100,主要当做百分比用
+注意:这个不是分贝,因此没用volume当做名称*/
+Recorder.PowerLevel=function(pcmAbsSum,pcmLength){
+	/*计算音量 https://blog.csdn.net/jody1989/article/details/73480259
+	更高灵敏度算法:
+		限定最大感应值10000
+			线性曲线:低音量不友好
+				power/10000*100 
+			对数曲线:低音量友好,但需限定最低感应值
+				(1+Math.log10(power/10000))*100
+	*/
+	var power=(pcmAbsSum/pcmLength) || 0;//NaN
+	var level;
+	if(power<1251){//1250的结果10%,更小的音量采用线性取值
+		level=Math.round(power/1250*10);
+	}else{
+		level=Math.round(Math.min(100,Math.max(0,(1+Math.log(power/10000)/Math.log(10))*100)));
+	};
+	return level;
+};
+
+/*计算音量,单位dBFS(满刻度相对电平)
+maxSample: 为16位pcm采样的绝对值中最大的一个(计算峰值音量),或者为pcm中所有采样的绝对值的平局值
+返回值:-100~0 (最大值0dB,最小值-100代替-∞)
+*/
+Recorder.PowerDBFS=function(maxSample){
+	var val=Math.max(0.1, maxSample||0),Pref=0x7FFF;
+	val=Math.min(val,Pref);
+	//https://www.logiclocmusic.com/can-you-tell-the-decibel/
+	//https://blog.csdn.net/qq_17256689/article/details/120442510
+	val=20*Math.log(val/Pref)/Math.log(10);
+	return Math.max(-100,Math.round(val));
+};
+
+
+
+
+//带时间的日志输出,可设为一个空函数来屏蔽日志输出
+//CLog(msg,errOrLogMsg, logMsg...) err为数字时代表日志类型1:error 2:log默认 3:warn,否则当做内容输出,第一个参数不能是对象因为要拼接时间,后面可以接无数个输出参数
+Recorder.CLog=function(msg,err){
+	if(typeof console!="object")return;
+	var now=new Date();
+	var t=("0"+now.getMinutes()).substr(-2)
+		+":"+("0"+now.getSeconds()).substr(-2)
+		+"."+("00"+now.getMilliseconds()).substr(-3);
+	var recID=this&&this.envIn&&this.envCheck&&this.id;
+	var arr=["["+t+" "+RecTxt+(recID?":"+recID:"")+"]"+msg];
+	var a=arguments,cwe=Recorder.CLog;
+	var i=2,fn=cwe.log||console.log;
+	if(IsNum(err)){
+		fn=err==1?cwe.error||console.error:err==3?cwe.warn||console.warn:fn;
+	}else{
+		i=1;
+	};
+	for(;i<a.length;i++){
+		arr.push(a[i]);
+	};
+	if(IsLoser){//古董浏览器,仅保证基本的可执行不代码异常
+		fn&&fn("[IsLoser]"+arr[0],arr.length>1?arr:"");
+	}else{
+		fn.apply(console,arr);
+	};
+};
+var CLog=function(){ Recorder.CLog.apply(this,arguments); };
+var IsLoser=true;try{IsLoser=!console.log.apply;}catch(e){};
+
+
+
+
+var ID=0;
+function initFn(set){
+	var This=this; This.id=++ID;
+	
+	//如果开启了流量统计,这里将发送一个图片请求
+	Traffic();
+	
+	
+	var o={
+		type:"mp3" //输出类型:mp3,wav,wav输出文件尺寸超大不推荐使用,但mp3编码支持会导致js文件超大,如果不需支持mp3可以使js文件大幅减小
+		//,bitRate:16 //比特率 wav:16或8位,MP3:8kbps 1k/s,8kbps 2k/s 录音文件很小
+		
+		//,sampleRate:16000 //采样率,wav格式大小=sampleRate*时间;mp3此项对低比特率有影响,高比特率几乎无影响。
+					//wav任意值,mp3取值范围:48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000
+					//采样率参考https://www.cnblogs.com/devin87/p/mp3-recorder.html
+		
+		,onProcess:NOOP //fn(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd) buffers=[[Int16,...],...]:缓冲的PCM数据,为从开始录音到现在的所有pcm片段;powerLevel:当前缓冲的音量级别0-100,bufferDuration:已缓冲时长,bufferSampleRate:缓冲使用的采样率(当type支持边录边转码(Worker)时,此采样率和设置的采样率相同,否则不一定相同);newBufferIdx:本次回调新增的buffer起始索引;asyncEnd:fn() 如果onProcess是异步的(返回值为true时),处理完成时需要调用此回调,如果不是异步的请忽略此参数,此方法回调时必须是真异步(不能真异步时需用setTimeout包裹)。onProcess返回值:如果返回true代表开启异步模式,在某些大量运算的场合异步是必须的,必须在异步处理完成时调用asyncEnd(不能真异步时需用setTimeout包裹),在onProcess执行后新增的buffer会全部替换成空数组,因此本回调开头应立即将newBufferIdx到本次回调结尾位置的buffer全部保存到另外一个数组内,处理完成后写回buffers中本次回调的结尾位置。
+		
+		//*******高级设置******
+		//,sourceStream:MediaStream Object
+				//可选直接提供一个媒体流,从这个流中录制、实时处理音频数据(当前Recorder实例独享此流);不提供时为普通的麦克风录音,由getUserMedia提供音频流(所有Recorder实例共享同一个流)
+				//比如:audio、video标签dom节点的captureStream方法(实验特性,不同浏览器支持程度不高)返回的流;WebRTC中的remote流;自己创建的流等
+				//注意:流内必须至少存在一条音轨(Audio Track),比如audio标签必须等待到可以开始播放后才会有音轨,否则open会失败
+		
+		//,runningContext:AudioContext
+				//可选提供一个state为running状态的AudioContext对象(ctx);默认会在rec.open时自动创建一个新的ctx,无用户操作(触摸、点击等)时调用rec.open的ctx.state可能为suspended,会在rec.start时尝试进行ctx.resume,如果也无用户操作ctx.resume可能不会恢复成running状态(目前仅iOS上有此兼容性问题),导致无法去读取媒体流,这时请提前在用户操作时调用Recorder.GetContext(true)来得到一个running状态AudioContext(用完需调用CloseNewCtx(ctx)关闭)
+		
+		//,audioTrackSet:{ deviceId:"",groupId:"", autoGainControl:true, echoCancellation:true, noiseSuppression:true }
+				//普通麦克风录音时getUserMedia方法的audio配置参数,比如指定设备id,回声消除、降噪开关;注意:提供的任何配置值都不一定会生效
+				//由于麦克风是全局共享的,所以新配置后需要close掉以前的再重新open
+				//更多参考: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints
+		
+		//,disableEnvInFix:false 内部参数,禁用设备卡顿时音频输入丢失补偿功能
+		
+		//,takeoffEncodeChunk:NOOP //fn(chunkBytes) chunkBytes=[Uint8,...]:实时编码环境下接管编码器输出,当编码器实时编码出一块有效的二进制音频数据时实时回调此方法;参数为二进制的Uint8Array,就是编码出来的音频数据片段,所有的chunkBytes拼接在一起即为完整音频。本实现的想法最初由QQ2543775048提出
+				//当提供此回调方法时,将接管编码器的数据输出,编码器内部将放弃存储生成的音频数据;如果当前编码器或环境不支持实时编码处理,将在open时直接走fail逻辑
+				//因此提供此回调后调用stop方法将无法获得有效的音频数据,因为编码器内没有音频数据,因此stop时返回的blob将是一个字节长度为0的blob
+				//大部分录音格式编码器都支持实时编码(边录边转码),比如mp3格式:会实时的将编码出来的mp3片段通过此方法回调,所有的chunkBytes拼接到一起即为完整的mp3,此种拼接的结果比mock方法实时生成的音质更加,因为天然避免了首尾的静默
+				//不支持实时编码的录音格式不可以提供此回调(wav格式不支持,因为wav文件头中需要提供文件最终长度),提供了将在open时直接走fail逻辑
+	};
+	
+	for(var k in set){
+		o[k]=set[k];
+	};
+	This.set=o;
+	
+	var vB=o[bitRateTxt],vS=o[sampleRateTxt]; //校验配置参数
+	if(vB&&!IsNum(vB) || vS&&!IsNum(vS)){
+		This.CLog($T.G("IllegalArgs-1",[$T("VtS4::{1}和{2}必须是数值",0,sampleRateTxt,bitRateTxt)]),1,set);
+	};
+	o[bitRateTxt]=+vB||16;
+	o[sampleRateTxt]=+vS||16000;
+	
+	This.state=0;//运行状态,0未录音 1录音中 2暂停 3等待ctx激活
+	This._S=9;//stop同步锁,stop可以阻止open过程中还未运行的start
+	This.Sync={O:9,C:9};//和Recorder.Sync一致,只不过这个是非全局的,仅用来简化代码逻辑,无实际作用
+};
+//同步锁,控制对Stream的竞争;用于close时中断异步的open;一个对象open如果变化了都要阻止close,Stream的控制权交个新的对象
+Recorder.Sync={/*open*/O:9,/*close*/C:9};
+
+Recorder.prototype=initFn.prototype={
+	CLog:CLog
+	
+	//流相关的数据存储在哪个对象里面;如果提供了sourceStream,数据直接存储在当前对象中,否则存储在全局
+	,_streamStore:function(){
+		if(this.set.sourceStream){
+			return this;
+		}else{
+			return Recorder;
+		}
+	}
+	//当前实例用到的AudioContext,可能是全局的,也可能是独享的
+	,_streamCtx:function(){
+		var m=this._streamStore().Stream;
+		return m&&m._c;
+	}
+	
+	//打开录音资源True(),False(msg,isUserNotAllow),需要调用close。注意:此方法是异步的;一般使用时打开,用完立即关闭;可重复调用,可用来测试是否能录音;open和start至少有一个应当在用户操作(触摸、点击等)下进行调用,原因参考runningContext配置
+	,open:function(True,False){
+		var This=this,set=This.set,streamStore=This._streamStore(),newCtx=0;
+		True=True||NOOP;
+		var failCall=function(errMsg,isUserNotAllow){
+			isUserNotAllow=!!isUserNotAllow;
+			This.CLog($T("5tWi::录音open失败:")+errMsg+",isUserNotAllow:"+isUserNotAllow,1);
+			if(newCtx)Recorder.CloseNewCtx(newCtx);
+			False&&False(errMsg,isUserNotAllow);
+		};
+		
+		This._streamTag=getUserMediaTxt;
+		var ok=function(){
+			This.CLog("open ok, id:"+This.id+" stream:"+This._streamTag);
+			True();
+			
+			This._SO=0;//解除stop对open中的start调用的阻止
+		};
+		
+		
+		//同步锁
+		var Lock=streamStore.Sync;
+		var lockOpen=++Lock.O,lockClose=Lock.C;
+		This._O=This._O_=lockOpen;//记住当前的open,如果变化了要阻止close,这里假定了新对象已取代当前对象并且不再使用
+		This._SO=This._S;//记住open过程中的stop,中途任何stop调用后都不能继续open中的start
+		var lockFail=function(){
+			//允许多次open,但不允许任何一次close,或者自身已经调用了关闭
+			if(lockClose!=Lock.C || !This._O){
+				var err=$T("dFm8::open被取消");
+				if(lockOpen==Lock.O){
+					//无新的open,已经调用了close进行取消,此处应让上次的close明确生效
+					This.close();
+				}else{
+					err=$T("VtJO::open被中断");
+				};
+				failCall(err);
+				return true;
+			};
+		};
+		
+		//环境配置检查
+		if(!isBrowser){
+			failCall($T.G("NonBrowser-1",["open"])+$T("EMJq::,可尝试使用RecordApp解决方案")+"("+GitUrl+"/tree/master/app-support-sample)");
+			return;
+		};
+		var checkMsg=This.envCheck({envName:"H5",canProcess:true});
+		if(checkMsg){
+			failCall($T("A5bm::不能录音:")+checkMsg);
+			return;
+		};
+		
+		
+		//***********已直接提供了音频流************
+		if(set.sourceStream){
+			This._streamTag="set.sourceStream";
+			if(!Recorder.GetContext()){
+				failCall($T("1iU7::不支持此浏览器从流中获取录音"));
+				return;
+			};
+			
+			Disconnect(streamStore);//可能已open过,直接先尝试断开
+			var stream=This.Stream=set.sourceStream;
+			stream._RC=set.runningContext;
+			stream._call={};
+			
+			try{
+				Connect(streamStore);
+			}catch(e){
+				Disconnect(streamStore);
+				failCall($T("BTW2::从流中打开录音失败:")+e.message);
+				return;
+			}
+			ok();
+			return;
+		};
+		
+		
+		//***********打开麦克风得到全局的音频流************
+		var codeFail=function(code,msg){
+			try{//跨域的优先检测一下
+				window.top.a;
+			}catch(e){
+				failCall($T("Nclz::无权录音(跨域,请尝试给iframe添加麦克风访问策略,如{1})",0,'allow="camera;microphone"'));
+				return;
+			};
+			
+			if(/Permission|Allow/i.test(code)){
+				failCall($T("gyO5::用户拒绝了录音权限"),true);
+			}else if(window.isSecureContext===false){
+				failCall($T("oWNo::浏览器禁止不安全页面录音,可开启https解决"));
+			}else if(/Found/i.test(code)){//可能是非安全环境导致的没有设备
+				failCall(msg+$T("jBa9::,无可用麦克风"));
+			}else{
+				failCall(msg);
+			};
+		};
+		
+		
+		//如果已打开并且有效就不要再打开了
+		if(Recorder.IsOpen()){
+			ok();
+			return;
+		};
+		if(!Recorder.Support()){
+			codeFail("",$T("COxc::此浏览器不支持录音"));
+			return;
+		};
+		//尽量先创建好ctx,不然异步下创建可能不是running状态
+		var ctx=set.runningContext;
+		if(!ctx)ctx=newCtx=Recorder.GetContext(true);
+		
+		//请求权限,如果从未授权,一般浏览器会弹出权限请求弹框
+		var f1=function(stream){
+			//https://github.com/xiangyuecn/Recorder/issues/14 获取到的track.readyState!="live",刚刚回调时可能是正常的,但过一下可能就被关掉了,原因不明。延迟一下保证真异步。对正常浏览器不影响
+			setTimeout(function(){
+				stream._call={};
+				var oldStream=Recorder.Stream;
+				if(oldStream){
+					Disconnect(); //直接断开已存在的,旧的Connect未完成会自动终止
+					stream._call=oldStream._call;
+				};
+				Recorder.Stream=stream;
+				stream._c=ctx;
+				stream._RC=set.runningContext;
+				if(lockFail())return;
+				
+				if(Recorder.IsOpen()){
+					if(oldStream)This.CLog($T("upb8::发现同时多次调用open"),1);
+					
+					Connect(streamStore,1);
+					ok();
+				}else{
+					failCall($T("Q1GA::录音功能无效:无音频流"));
+				};
+			},100);
+		};
+		var f2=function(e){
+			var code=e.name||e.message||e.code+":"+e;
+			This.CLog($T("xEQR::请求录音权限错误"),1,e);
+			
+			codeFail(code,$T("bDOG::无法录音:")+code);
+		};
+		
+		var trackSet=set.audioTrackSet||{};
+		trackSet[sampleRateTxt]=ctx[sampleRateTxt];//必须指明采样率,不然手机上MediaRecorder采样率16k
+		
+		var mSet={audio:trackSet};
+		try{
+			var pro=Recorder.Scope[getUserMediaTxt](mSet,f1,f2);
+		}catch(e){//不能设置trackSet就算了
+			This.CLog(getUserMediaTxt,3,e);
+			mSet={audio:true};
+			pro=Recorder.Scope[getUserMediaTxt](mSet,f1,f2);
+		};
+		This.CLog(getUserMediaTxt+"("+JSON.stringify(mSet)+") "+CtxState(ctx)
+			+$T("RiWe::,未配置noiseSuppression和echoCancellation时浏览器可能会自动打开降噪和回声消除,移动端可能会降低系统播放音量(关闭录音后可恢复),请参阅文档中audioTrackSet配置")
+			+"("+GitUrl+") LM:"+LM+" UA:"+navigator.userAgent);
+		if(pro&&pro.then){
+			pro.then(f1)[CatchTxt](f2); //fix 关键字,保证catch压缩时保持字符串形式
+		};
+	}
+	//关闭释放录音资源
+	,close:function(call){
+		call=call||NOOP;
+		
+		var This=this,streamStore=This._streamStore();
+		This._stop();
+		var sTag=" stream:"+This._streamTag;
+		
+		var Lock=streamStore.Sync;
+		This._O=0;
+		if(This._O_!=Lock.O){
+			//唯一资源Stream的控制权已交给新对象,这里不能关闭。此处在每次都弹权限的浏览器内可能存在泄漏,新对象被拒绝权限可能不会调用close,忽略这种不处理
+			This.CLog($T("hWVz::close被忽略(因为同时open了多个rec,只有最后一个会真正close)")+sTag,3);
+			call();
+			return;
+		};
+		Lock.C++;//获得控制权
+		
+		Disconnect(streamStore);
+		
+		This.CLog("close,"+sTag);
+		call();
+	}
+	
+	
+	
+	
+	
+	/*模拟一段录音数据,后面可以调用stop进行编码,需提供pcm数据[1,2,3...],pcm的采样率*/
+	,mock:function(pcmData,pcmSampleRate){
+		var This=this;
+		This._stop();//清理掉已有的资源
+		
+		This.isMock=1;
+		This.mockEnvInfo=null;
+		This.buffers=[pcmData];
+		This.recSize=pcmData.length;
+		This._setSrcSR(pcmSampleRate);
+		This._streamTag="mock";
+		return This;
+	}
+	,_setSrcSR:function(sampleRate){
+		var This=this,set=This.set;
+		var setSr=set[sampleRateTxt];
+		if(setSr>sampleRate){
+			set[sampleRateTxt]=sampleRate;
+		}else{ setSr=0 }
+		This[srcSampleRateTxt]=sampleRate;
+		This.CLog(srcSampleRateTxt+": "+sampleRate+" set."+sampleRateTxt+": "+set[sampleRateTxt]+(setSr?" "+$T("UHvm::忽略")+": "+setSr:""), setSr?3:0);
+	}
+	,envCheck:function(envInfo){//平台环境下的可用性检查,任何时候都可以调用检查,返回errMsg:""正常,"失败原因"
+		//envInfo={envName:"H5",canProcess:true}
+		var errMsg,This=this,set=This.set;
+		
+		//检测CPU的数字字节序,TypedArray字节序是个迷,直接拒绝罕见的大端模式,因为找不到这种CPU进行测试
+		var tag="CPU_BE";
+		if(!errMsg && !Recorder[tag] && typeof Int8Array=="function" && !new Int8Array(new Int32Array([1]).buffer)[0]){
+			Traffic(tag); //如果开启了流量统计,这里将发送一个图片请求
+			errMsg=$T("Essp::不支持{1}架构",0,tag);
+		};
+		
+		//编码器检查环境下配置是否可用
+		if(!errMsg){
+			var type=set.type,hasFn=This[type+"_envCheck"];
+			if(set.takeoffEncodeChunk){//需要实时编码返回数据,此时需要检查环境是否有实时特性、和是否可实时编码
+				if(!hasFn){
+					errMsg=$T("2XBl::{1}类型不支持设置takeoffEncodeChunk",0,type)+(This[type]?"":$T("LG7e::(未加载编码器)"));
+				}else if(!envInfo.canProcess){
+					errMsg=$T("7uMV::{1}环境不支持实时处理",0,envInfo.envName);
+				};
+			};
+			
+			if(!errMsg && hasFn){//编码器已实现环境检查
+				errMsg=This[type+"_envCheck"](envInfo,set);
+			};
+		};
+		
+		return errMsg||"";
+	}
+	,envStart:function(mockEnvInfo,sampleRate){//平台环境相关的start调用
+		var This=this,set=This.set;
+		This.isMock=mockEnvInfo?1:0;//非H5环境需要启用mock,并提供envCheck需要的环境信息
+		This.mockEnvInfo=mockEnvInfo;
+		This.buffers=[];//数据缓冲
+		This.recSize=0;//数据大小
+		if(mockEnvInfo){
+			This._streamTag="env$"+mockEnvInfo.envName;
+		};
+		
+		This.state=1;//运行状态,0未录音 1录音中 2暂停 3等待ctx激活
+		This.envInLast=0;//envIn接收到最后录音内容的时间
+		This.envInFirst=0;//envIn接收到的首个录音内容的录制时间
+		This.envInFix=0;//补偿的总时间
+		This.envInFixTs=[];//补偿计数列表
+		
+		//engineCtx需要提前确定最终的采样率
+		This._setSrcSR(sampleRate);
+		
+		This.engineCtx=0;
+		//此类型有边录边转码(Worker)支持
+		if(This[set.type+"_start"]){
+			var engineCtx=This.engineCtx=This[set.type+"_start"](set);
+			if(engineCtx){
+				engineCtx.pcmDatas=[];
+				engineCtx.pcmSize=0;
+			};
+		};
+	}
+	,envResume:function(){//和平台环境无关的恢复录音
+		//重新开始计数
+		this.envInFixTs=[];
+	}
+	,envIn:function(pcm,sum){//和平台环境无关的pcm[Int16]输入
+		var This=this,set=This.set,engineCtx=This.engineCtx;
+		if(This.state!=1){
+			if(!This.state)This.CLog("envIn at state=0",3);
+			return;
+		};
+		var bufferSampleRate=This[srcSampleRateTxt];
+		var size=pcm.length;
+		var powerLevel=Recorder.PowerLevel(sum,size);
+		
+		var buffers=This.buffers;
+		var bufferFirstIdx=buffers.length;//之前的buffer都是经过onProcess处理好的,不允许再修改
+		buffers.push(pcm);
+		
+		//有engineCtx时会被覆盖,这里保存一份
+		var buffersThis=buffers;
+		var bufferFirstIdxThis=bufferFirstIdx;
+		
+		//卡顿丢失补偿:因为设备很卡的时候导致H5接收到的数据量不够造成播放时候变速,结果比实际的时长要短,此处保证了不会变短,但不能修复丢失的音频数据造成音质变差。当前算法采用输入时间侦测下一帧是否需要添加补偿帧,需要(6次输入||超过1秒)以上才会开始侦测,如果滑动窗口内丢失超过1/3就会进行补偿
+		var now=Date.now();
+		var pcmTime=Math.round(size/bufferSampleRate*1000);
+		This.envInLast=now;
+		if(This.buffers.length==1){//记下首个录音数据的录制时间
+			This.envInFirst=now-pcmTime;
+		};
+		var envInFixTs=This.envInFixTs;
+		envInFixTs.splice(0,0,{t:now,d:pcmTime});
+		//保留3秒的计数滑动窗口,另外超过3秒的停顿不补偿
+		var tsInStart=now,tsPcm=0;
+		for(var i=0;i<envInFixTs.length;i++){
+			var o=envInFixTs[i];
+			if(now-o.t>3000){
+				envInFixTs.length=i;
+				break;
+			};
+			tsInStart=o.t;
+			tsPcm+=o.d;
+		};
+		//达到需要的数据量,开始侦测是否需要补偿
+		var tsInPrev=envInFixTs[1];
+		var tsIn=now-tsInStart;
+		var lost=tsIn-tsPcm;
+		if( lost>tsIn/3 && (tsInPrev&&tsIn>1000 || envInFixTs.length>=6) ){
+			//丢失过多,开始执行补偿
+			var addTime=now-tsInPrev.t-pcmTime;//距离上次输入丢失这么多ms
+			if(addTime>pcmTime/5){//丢失超过本帧的1/5
+				var fixOpen=!set.disableEnvInFix;
+				This.CLog("["+now+"]"+i18n.get(fixOpen?$T("4Kfd::补偿{1}ms",1):$T("bM5i::未补偿{1}ms",1),[addTime]),3);
+				This.envInFix+=addTime;
+				
+				//用静默进行补偿
+				if(fixOpen){
+					var addPcm=new Int16Array(addTime*bufferSampleRate/1000);
+					size+=addPcm.length;
+					buffers.push(addPcm);
+				};
+			};
+		};
+		
+		
+		var sizeOld=This.recSize,addSize=size;
+		var bufferSize=sizeOld+addSize;
+		This.recSize=bufferSize;//此值在onProcess后需要修正,可能新数据被修改
+		
+		
+		//此类型有边录边转码(Worker)支持,开启实时转码
+		if(engineCtx){
+			//转换成set的采样率
+			var chunkInfo=Recorder.SampleData(buffers,bufferSampleRate,set[sampleRateTxt],engineCtx.chunkInfo);
+			engineCtx.chunkInfo=chunkInfo;
+			
+			sizeOld=engineCtx.pcmSize;
+			addSize=chunkInfo.data.length;
+			bufferSize=sizeOld+addSize;
+			engineCtx.pcmSize=bufferSize;//此值在onProcess后需要修正,可能新数据被修改
+			
+			buffers=engineCtx.pcmDatas;
+			bufferFirstIdx=buffers.length;
+			buffers.push(chunkInfo.data);
+			bufferSampleRate=chunkInfo[sampleRateTxt];
+		};
+		
+		var duration=Math.round(bufferSize/bufferSampleRate*1000);
+		var bufferNextIdx=buffers.length;
+		var bufferNextIdxThis=buffersThis.length;
+		
+		//允许异步处理buffer数据
+		var asyncEnd=function(){
+			//重新计算size,异步的早已减去添加的,同步的需去掉本次添加的然后重新计算
+			var num=asyncBegin?0:-addSize;
+			var hasClear=buffers[0]==null;
+			for(var i=bufferFirstIdx;i<bufferNextIdx;i++){
+				var buffer=buffers[i];
+				if(buffer==null){//已被主动释放内存,比如长时间实时传输录音时
+					hasClear=1;
+				}else{
+					num+=buffer.length;
+					
+					//推入后台边录边转码
+					if(engineCtx&&buffer.length){
+						This[set.type+"_encode"](engineCtx,buffer);
+					};
+				};
+			};
+			
+			//同步清理This.buffers,不管buffers到底清了多少个,buffersThis是使用不到的进行全清
+			if(hasClear && engineCtx){
+				var i=bufferFirstIdxThis;
+				if(buffersThis[0]){
+					i=0;
+				};
+				for(;i<bufferNextIdxThis;i++){
+					buffersThis[i]=null;
+				};
+			};
+			
+			//统计修改后的size,如果异步发生clear要原样加回来,同步的无需操作
+			if(hasClear){
+				num=asyncBegin?addSize:0;
+				
+				buffers[0]=null;//彻底被清理
+			};
+			if(engineCtx){
+				engineCtx.pcmSize+=num;
+			}else{
+				This.recSize+=num;
+			};
+		};
+		//实时回调处理数据,允许修改或替换上次回调以来新增的数据 ,但是不允许修改已处理过的,不允许增删第一维数组 ,允许将第二维数组任意修改替换成空数组也可以
+		var asyncBegin=0,procTxt="rec.set.onProcess";
+		try{
+			asyncBegin=set.onProcess(buffers,powerLevel,duration,bufferSampleRate,bufferFirstIdx,asyncEnd);
+		}catch(e){
+			//此错误显示不要用CLog,这样控制台内相同内容不会重复打印
+			console.error(procTxt+$T("gFUF::回调出错是不允许的,需保证不会抛异常"),e);
+		};
+		
+		var slowT=Date.now()-now;
+		if(slowT>10 && This.envInFirst-now>1000){ //1秒后开始onProcess性能监测
+			This.CLog(procTxt+$T("2ghS::低性能,耗时{1}ms",0,slowT),3);
+		};
+		
+		if(asyncBegin===true){
+			//开启了异步模式,onProcess已接管buffers新数据,立即清空,避免出现未处理的数据
+			var hasClear=0;
+			for(var i=bufferFirstIdx;i<bufferNextIdx;i++){
+				if(buffers[i]==null){//已被主动释放内存,比如长时间实时传输录音时 ,但又要开启异步模式,此种情况是非法的
+					hasClear=1;
+				}else{
+					buffers[i]=new Int16Array(0);
+				};
+			};
+			
+			if(hasClear){
+				This.CLog($T("ufqH::未进入异步前不能清除buffers"),3);
+			}else{
+				//还原size,异步结束后再统计仅修改后的size,如果发生clear要原样加回来
+				if(engineCtx){
+					engineCtx.pcmSize-=addSize;
+				}else{
+					This.recSize-=addSize;
+				};
+			};
+		}else{
+			asyncEnd();
+		};
+	}
+	
+	
+	
+	
+	//开始录音,需先调用open;只要open成功时,调用此方法是安全的,如果未open强行调用导致的内部错误将不会有任何提示,stop时自然能得到错误;注意:open和start至少有一个应当在用户操作(触摸、点击等)下进行调用,原因参考runningContext配置
+	,start:function(){
+		var This=this;
+		
+		var isOpen=1;
+		if(This.set.sourceStream){//直接提供了流,仅判断是否调用了open
+			if(!This.Stream){
+				isOpen=0;
+			}
+		}else if(!Recorder.IsOpen()){//监测全局麦克风是否打开并且有效
+			isOpen=0;
+		};
+		if(!isOpen){
+			This.CLog($T("6WmN::start失败:未open"),1);
+			return;
+		};
+		var ctx=This._streamCtx();
+		This.CLog($T("kLDN::start 开始录音,")+CtxState(ctx)+" stream:"+This._streamTag);
+		
+		This._stop();
+		This.envStart(null, ctx[sampleRateTxt]);
+		This.state=3;//0未录音 1录音中 2暂停 3等待ctx激活
+		
+		//检查open过程中stop是否已经调用过
+		if(This._SO&&This._SO+1!=This._S){//上面调用过一次 _stop
+			//open未完成就调用了stop,此种情况终止start。也应尽量避免出现此情况
+			This.CLog($T("Bp2y::start被中断"),3);
+			return;
+		};
+		This._SO=0;
+		
+		var end=function(){
+			if(This.state==3){
+				This.state=1;
+				This.resume();
+			}
+		};
+		var tag="AudioContext resume: ";
+		ResumeCtx(ctx,function(runC){
+			runC&&This.CLog(tag+"wait...");
+			return This.state==3;
+		},function(runC){
+			runC&&This.CLog(tag+ctx.state);
+			end();
+		},function(err){ //比较少见,可能对录音没有影响
+			This.CLog(tag+ctx.state+$T("upkE::,可能无法录音:")+err,1);
+			end();
+		});
+	}
+	
+	
+	
+	/*暂停录音*/
+	,pause:function(){
+		var This=this,stream=This._streamStore().Stream;
+		if(This.state){
+			This.state=2;
+			This.CLog("pause");
+			if(stream) delete stream._call[This.id];
+		};
+	}
+	/*恢复录音*/
+	,resume:function(){
+		var This=this,stream=This._streamStore().Stream;
+		var tag="resume",tag3=tag+"(wait ctx)";
+		if(This.state==3){ //start还在等ctx恢复
+			This.CLog(tag3);
+		}else if(This.state){
+			This.state=1;
+			This.CLog(tag);
+			This.envResume();
+			
+			if(stream){
+				stream._call[This.id]=function(pcm,sum){
+					if(This.state==1){
+						This.envIn(pcm,sum);
+					};
+				};
+				ConnAlive(stream);//AudioWorklet只会在ctx激活后运行
+			};
+			
+			var ctx=This._streamCtx();
+			if(ctx){ //AudioContext如果被暂停,尽量恢复
+				ResumeCtx(ctx,function(runC){
+					runC&&This.CLog(tag3+"...");
+					return This.state==1;
+				},function(runC){
+					runC&&This.CLog(tag3+ctx.state);
+					ConnAlive(stream);
+				},function(err){
+					This.CLog(tag3+ctx.state+"[err]"+err,1);
+				});
+			};
+		};
+	}
+	
+	
+	
+	
+	,_stop:function(keepEngine){
+		var This=this,set=This.set;
+		if(!This.isMock){
+			This._S++;
+		};
+		if(This.state){
+			This.pause();
+			This.state=0;
+		};
+		if(!keepEngine && This[set.type+"_stop"]){
+			This[set.type+"_stop"](This.engineCtx);
+			This.engineCtx=0;
+		};
+	}
+	/*
+	结束录音并返回录音数据blob对象
+		True(blob,duration,mime)
+			blob:录音数据audio/mp3|wav格式;默认是Blob对象,可设置rec.dataType="arraybuffer"改成ArrayBuffer
+			duration:录音时长,单位毫秒
+			mime:"auido/mp3" blob数据的类型,方便ArrayBuffer时区分类型
+		False(msg)
+		autoClose:false 可选,是否自动调用close,默认为false
+	*/
+	,stop:function(True,False,autoClose){
+		var This=this,set=This.set,t1;
+		var envInMS=This.envInLast-This.envInFirst, envInLen=envInMS&&This.buffers.length; //可能未start
+		This.CLog($T("Xq4s::stop 和start时差:")
+			+(envInMS?envInMS+"ms "+$T("3CQP::补偿:")+This.envInFix+"ms"
+				+" envIn:"+envInLen+" fps:"+(envInLen/envInMS*1000).toFixed(1)
+			:"-")+" stream:"+This._streamTag+" ("+GitUrl+") LM:"+LM);
+		
+		var end=function(){
+			This._stop();//彻底关掉engineCtx
+			if(autoClose){
+				This.close();
+			};
+		};
+		var err=function(msg){
+			This.CLog($T("u8JG::结束录音失败:")+msg,1);
+			False&&False(msg);
+			end();
+		};
+		var ok=function(blob,mime,duration){
+			var tBlob="blob",tABuf="arraybuffer",tDT="dataType",tDDT="DefaultDataType";
+			var dType=This[tDT]||Recorder[tDDT]||tBlob,dTag=tDT+"="+dType;
+			var isAB=blob instanceof ArrayBuffer,dErr=0;
+			var dLen=isAB?blob.byteLength:blob.size;
+			if(dType==tABuf){
+				if(!isAB) dErr=1;
+			}else if(dType==tBlob){
+				if(typeof Blob!="function"){
+					dErr=$T.G("NonBrowser-1",[dTag])+$T("1skY::,请设置{1}",0,RecTxt+"."+tDDT+'="'+tABuf+'"');
+				}else{
+					if(isAB) blob=new Blob([blob],{type:mime});
+					if(!(blob instanceof Blob)) dErr=1;
+					mime=blob.type||mime;
+				}
+			}else{
+				dErr=$T.G("NotSupport-1",[dTag]);
+			};
+			
+			This.CLog($T("Wv7l::结束录音 编码花{1}ms 音频时长{2}ms 文件大小{3}b",0,Date.now()-t1,duration,dLen)+" "+dTag+","+mime);
+			if(dErr){
+				err(dErr!=1?dErr:$T("Vkbd::{1}编码器返回的不是{2}",0,set.type,dType)+", "+dTag);
+				return;
+			};
+			if(set.takeoffEncodeChunk){//接管了输出,此时blob长度为0
+				This.CLog($T("QWnr::启用takeoffEncodeChunk后stop返回的blob长度为0不提供音频数据"),3);
+			}else if(dLen<Math.max(50,duration/5)){//1秒小于0.2k?
+				err($T("Sz2H::生成的{1}无效",0,set.type));
+				return;
+			};
+			
+			True&&True(blob,duration,mime);
+			end();
+		};
+		if(!This.isMock){
+			var isCtxWait=This.state==3;
+			if(!This.state || isCtxWait){
+				err($T("wf9t::未开始录音")+(isCtxWait?$T("Dl2c::,开始录音前无用户交互导致AudioContext未运行"):""));
+				return;
+			};
+		};
+		This._stop(true); //将录音状态改成未录音
+		var size=This.recSize;
+		if(!size){
+			err($T("Ltz3::未采集到录音"));
+			return;
+		};
+		if(!This[set.type]){
+			err($T("xGuI::未加载{1}编码器,请尝试到{2}的src/engine内找到{1}的编码器并加载",0,set.type,RecTxt));
+			return;
+		};
+		
+		//环境配置检查,此处仅针对mock调用,因为open已经检查过了
+		if(This.isMock){
+			var checkMsg=This.envCheck(This.mockEnvInfo||{envName:"mock",canProcess:false});//没有提供环境信息的mock时没有onProcess回调
+			if(checkMsg){
+				err($T("AxOH::录音错误:")+checkMsg);
+				return;
+			};
+		};
+		
+		//此类型有边录边转码(Worker)支持
+		var engineCtx=This.engineCtx;
+		if(This[set.type+"_complete"]&&engineCtx){
+			var duration=Math.round(engineCtx.pcmSize/set[sampleRateTxt]*1000);//采用后的数据长度和buffers的长度可能微小的不一致,是采样率连续转换的精度问题
+			
+			t1=Date.now();
+			This[set.type+"_complete"](engineCtx,function(blob,mime){
+				ok(blob,mime,duration);
+			},err);
+			return;
+		};
+		
+		//标准UI线程转码,调整采样率
+		t1=Date.now();
+		if(!This.buffers[0]){
+			err($T("xkKd::音频buffers被释放"));
+			return;
+		};
+		var chunk=Recorder.SampleData(This.buffers,This[srcSampleRateTxt],set[sampleRateTxt]);
+		
+		set[sampleRateTxt]=chunk[sampleRateTxt];
+		var res=chunk.data;
+		var duration=Math.round(res.length/set[sampleRateTxt]*1000);
+		
+		This.CLog($T("CxeT::采样:{1} 花:{2}ms",0,size+"->"+res.length,Date.now()-t1));
+		
+		setTimeout(function(){
+			t1=Date.now();
+			This[set.type](res,function(blob,mime){
+				ok(blob,mime,duration);
+			},function(msg){
+				err(msg);
+			});
+		});
+	}
+
+};
+
+
+
+
+
+//=======从WebM字节流中提取pcm数据,提取成功返回Float32Array,失败返回null||-1=====
+var WebM_Extract=function(inBytes, scope){
+	if(!scope.pos){
+		scope.pos=[0]; scope.tracks={}; scope.bytes=[];
+	};
+	var tracks=scope.tracks, position=[scope.pos[0]];
+	var endPos=function(){ scope.pos[0]=position[0] };
+	
+	var sBL=scope.bytes.length;
+	var bytes=new Uint8Array(sBL+inBytes.length);
+	bytes.set(scope.bytes); bytes.set(inBytes,sBL);
+	scope.bytes=bytes;
+	
+	//先读取文件头和Track信息
+	if(!scope._ht){
+		readMatroskaVInt(bytes, position);//EBML Header
+		readMatroskaBlock(bytes, position);//跳过EBML Header内容
+		if(!BytesEq(readMatroskaVInt(bytes, position), [0x18,0x53,0x80,0x67])){
+			return;//未识别到Segment
+		}
+		readMatroskaVInt(bytes, position);//跳过Segment长度值
+		while(position[0]<bytes.length){
+			var eid0=readMatroskaVInt(bytes, position);
+			var bytes0=readMatroskaBlock(bytes, position);
+			var pos0=[0],audioIdx=0;
+			if(!bytes0)return;//数据不全,等待缓冲
+			//Track完整数据,循环读取TrackEntry
+			if(BytesEq(eid0, [0x16,0x54,0xAE,0x6B])){
+				while(pos0[0]<bytes0.length){
+					var eid1=readMatroskaVInt(bytes0, pos0);
+					var bytes1=readMatroskaBlock(bytes0, pos0);
+					var pos1=[0],track={channels:0,sampleRate:0};
+					if(BytesEq(eid1, [0xAE])){//TrackEntry
+						while(pos1[0]<bytes1.length){
+							var eid2=readMatroskaVInt(bytes1, pos1);
+							var bytes2=readMatroskaBlock(bytes1, pos1);
+							var pos2=[0];
+							if(BytesEq(eid2, [0xD7])){//Track Number
+								var val=BytesInt(bytes2);
+								track.number=val;
+								tracks[val]=track;
+							}else if(BytesEq(eid2, [0x83])){//Track Type
+								var val=BytesInt(bytes2);
+								if(val==1) track.type="video";
+								else if(val==2) {
+									track.type="audio";
+									if(!audioIdx) scope.track0=track;
+									track.idx=audioIdx++;
+								}else track.type="Type-"+val;
+							}else if(BytesEq(eid2, [0x86])){//Track Codec
+								var str="";
+								for(var i=0;i<bytes2.length;i++){
+									str+=String.fromCharCode(bytes2[i]);
+								}
+								track.codec=str;
+							}else if(BytesEq(eid2, [0xE1])){
+								while(pos2[0]<bytes2.length){//循环读取 Audio 属性
+									var eid3=readMatroskaVInt(bytes2, pos2);
+									var bytes3=readMatroskaBlock(bytes2, pos2);
+									//采样率、位数、声道数
+									if(BytesEq(eid3, [0xB5])){
+										var val=0,arr=new Uint8Array(bytes3.reverse()).buffer;
+										if(bytes3.length==4) val=new Float32Array(arr)[0];
+										else if(bytes3.length==8) val=new Float64Array(arr)[0];
+										else CLog("WebM Track !Float",1,bytes3);
+										track[sampleRateTxt]=Math.round(val);
+									}else if(BytesEq(eid3, [0x62,0x64])) track.bitDepth=BytesInt(bytes3);
+									else if(BytesEq(eid3, [0x9F])) track.channels=BytesInt(bytes3);
+								}
+							}
+						}
+					}
+				};
+				scope._ht=1;
+				CLog("WebM Tracks",tracks);
+				endPos();
+				break;
+			}
+		}
+	}
+	
+	//校验音频参数信息,如果不符合代码要求,统统拒绝处理
+	var track0=scope.track0;
+	if(!track0)return;
+	if(track0.bitDepth==16 && /FLOAT/i.test(track0.codec)){
+		track0.bitDepth=32; //chrome v66 实际为浮点数
+		CLog("WebM 16->32 bit",3);
+	}
+	if(track0[sampleRateTxt]!=scope[sampleRateTxt] || track0.bitDepth!=32 || track0.channels<1 || !/(\b|_)PCM\b/i.test(track0.codec)){
+		scope.bytes=[];//格式非预期 无法处理,清空缓冲数据
+		if(!scope.bad)CLog("WebM Track Unexpected",3,scope);
+		scope.bad=1;
+		return -1;
+	}
+	
+	//循环读取Cluster内的SimpleBlock
+	var datas=[],dataLen=0;
+	while(position[0]<bytes.length){
+		var eid1=readMatroskaVInt(bytes, position);
+		var bytes1=readMatroskaBlock(bytes, position);
+		if(!bytes1)break;//数据不全,等待缓冲
+		if(BytesEq(eid1, [0xA3])){//SimpleBlock完整数据
+			var trackNo=bytes1[0]&0xf;
+			var track=tracks[trackNo];
+			if(!track){//不可能没有,数据出错?
+				CLog("WebM !Track"+trackNo,1,tracks);
+			}else if(track.idx===0){
+				var u8arr=new Uint8Array(bytes1.length-4);
+				for(var i=4;i<bytes1.length;i++){
+					u8arr[i-4]=bytes1[i];
+				}
+				datas.push(u8arr); dataLen+=u8arr.length;
+			}
+		}
+		endPos();
+	}
+	
+	if(dataLen){
+		var more=new Uint8Array(bytes.length-scope.pos[0]);
+		more.set(bytes.subarray(scope.pos[0]));
+		scope.bytes=more; //清理已读取了的缓冲数据
+		scope.pos[0]=0;
+		
+		var u8arr=new Uint8Array(dataLen); //已获取的音频数据
+		for(var i=0,i2=0;i<datas.length;i++){
+			u8arr.set(datas[i],i2);
+			i2+=datas[i].length;
+		}
+		var arr=new Float32Array(u8arr.buffer);
+		
+		if(track0.channels>1){//多声道,提取一个声道
+			var arr2=[];
+			for(var i=0;i<arr.length;){
+				arr2.push(arr[i]);
+				i+=track0.channels;
+			}
+			arr=new Float32Array(arr2);
+		};
+		return arr;
+	}
+};
+//两个字节数组内容是否相同
+var BytesEq=function(bytes1,bytes2){
+	if(!bytes1 || bytes1.length!=bytes2.length) return false;
+	if(bytes1.length==1) return bytes1[0]==bytes2[0];
+	for(var i=0;i<bytes1.length;i++){
+		if(bytes1[i]!=bytes2[i]) return false;
+	}
+	return true;
+};
+//字节数组BE转成int数字
+var BytesInt=function(bytes){
+	var s="";//0-8字节,js位运算只支持4字节
+	for(var i=0;i<bytes.length;i++){var n=bytes[i];s+=(n<16?"0":"")+n.toString(16)};
+	return parseInt(s,16)||0;
+};
+//读取一个可变长数值字节数组
+var readMatroskaVInt=function(arr,pos,trim){
+	var i=pos[0];
+	if(i>=arr.length)return;
+	var b0=arr[i],b2=("0000000"+b0.toString(2)).substr(-8);
+	var m=/^(0*1)(\d*)$/.exec(b2);
+	if(!m)return;
+	var len=m[1].length, val=[];
+	if(i+len>arr.length)return;
+	for(var i2=0;i2<len;i2++){ val[i2]=arr[i]; i++; }
+	if(trim) val[0]=parseInt(m[2]||'0',2);
+	pos[0]=i;
+	return val;
+};
+//读取一个自带长度的内容字节数组
+var readMatroskaBlock=function(arr,pos){
+	var lenVal=readMatroskaVInt(arr,pos,1);
+	if(!lenVal)return;
+	var len=BytesInt(lenVal);
+	var i=pos[0], val=[];
+	if(len<0x7FFFFFFF){ //超大值代表没有长度
+		if(i+len>arr.length)return;
+		for(var i2=0;i2<len;i2++){ val[i2]=arr[i]; i++; }
+	}
+	pos[0]=i;
+	return val;
+};
+//=====End WebM读取=====
+
+
+//=====i18n 简版国际化多语言支持=====
+var i18n=Recorder.i18n={
+	lang: "zh-CN" //默认中文
+	,alias:{"zh-CN":"zh","en-US":"en"} //别名配置,映射到一个语言实例
+	,locales:{} //语言实例:zh:{key:"text"}
+	,data:{} //各种数据,desc$xx:语言描述,rtl$xx:文本方向是否从右到左 rtl$zh:false rtl$ar:true
+	//添加语言文本内容,set={lang:"",overwrite:true},texts=["key:text{1}",...]
+	,put:function(set,texts){
+		var tag=RecTxt+".i18n.put: ";
+		var overwrite=set.overwrite; overwrite=overwrite==null||overwrite;
+		var lang=set.lang; lang=i18n.alias[lang]||lang;
+		if(!lang)throw new Error(tag+"set.lang?");
+		var locale=i18n.locales[lang];
+		if(!locale){ locale={}; i18n.locales[lang]=locale };
+		var exp=/^([\w\-]+):/,m;
+		for(var i=0;i<texts.length;i++){
+			var v=texts[i]; m=exp.exec(v);
+			if(!m){ CLog(tag+"'key:'? "+v,3,set); continue }
+			var key=m[1],v=v.substr(key.length+1);
+			if(!overwrite && locale[key]) continue;
+			locale[key]=v;
+		}
+	}
+	//获取key对应的文本,如果没有对应文本,将返回en的,en的也没有将返回zh的
+	,get:function(/*key,args,lang*/){
+		return i18n.v_G.apply(null,arguments);
+	}, v_G:function(key,args,lang){ //全局可重写get
+		args=args||[];
+		lang=lang||i18n.lang; lang=i18n.alias[lang]||lang;
+		var locale=i18n.locales[lang];
+		var val=locale&&locale[key]||"";
+		if(!val&&lang!="zh"){
+			if(lang=="en")return i18n.v_G(key,args,"zh");
+			return i18n.v_G(key,args,"en");
+		}
+		i18n.lastLang=lang;
+		if(val=="=Empty")return "";
+		return val.replace(/\{(\d+)(\!?)\}/g,function(v,a,b){
+			a=+a||0; v=args[a-1];
+			if(a<1 || a>args.length){ v="{?}"; CLog("i18n["+key+"] no {"+a+"}: "+val,3) }
+			return b?"":v;
+		});
+	}
+	/**返回一个国际化文本,返回的文本取决于i18n.lang。
+		调用参数:T("key:[lang]:中文{1}","[lang]:英文{1}",...,0,"args1","args2"),除了key:,其他都是可选的,文本如果在语言实例中不存在会push进去,第一个文本无lang时默认zh,第二个无lang时默认en,文本中用{1-n}来插入args
+		第一个args前面这个参数必须是0;也可以不提供args,这个参数填args的数量,此时不返回文本,只返回key,再用i18n.get提供参数获取文本
+		本函数调用,第一个文本需中文,调用尽量简单,不要换行,方便后续自动提取翻译列表
+		args如果旧的参数位置发生了变更,应当使用新的key,让旧的翻译失效,增加args无需更换key
+		key的生成使用随机字符串,不同源码里面可以搞个不同前缀:
+			s="";L=4; for(var i=0,n;i<L;i++){ n=~~(Math.random()*62);s+=n<10?n:String.fromCharCode(n<36?55+n:61+n); }; s
+	**/
+	,$T:function(){
+		return i18n.v_T.apply(null,arguments);
+	} ,v_T:function(){ //全局可重写$T
+		var a=arguments,key="",args=[],isArgs=0,tag=RecTxt+".i18n.$T:";
+		var exp=/^([\w\-]*):/,m;
+		for(var i=0;i<a.length;i++){
+			var v=a[i];
+			if(i==0){
+				m=exp.exec(v); key=m&&m[1];
+				if(!key)throw new Error(tag+"0 'key:'?");
+				v=v.substr(key.length+1);
+			}
+			if(isArgs===-1) args.push(v);
+			else if(isArgs) throw new Error(tag+" bad args");
+			else if(v===0) isArgs=-1;
+			else if(IsNum(v)){
+				if(v<1) throw new Error(tag+" bad args");
+				isArgs=v;
+			}else{
+				var lang=i==1?"en":i?"":"zh";
+				m=exp.exec(v); if(m){ lang=m[1]||lang; v=v.substr(m[1].length+1); }
+				if(!m || !lang)throw new Error(tag+i+" 'lang:'?");
+				i18n.put({lang:lang,overwrite:false},[key+":"+v]);
+			}
+		}
+		if(!key)return "";
+		if(isArgs>0)return key;
+		return i18n.v_G(key,args);
+	}
+};
+var $T=i18n.$T; $T.G=i18n.get;
+//预定义文本
+$T("NonBrowser-1::非浏览器环境,不支持{1}",1);
+$T("IllegalArgs-1::参数错误:{1}",1);
+$T("NeedImport-2::调用{1}需要先导入{2}",2);
+$T("NotSupport-1::不支持:{1}",1);
+//=====End i18n=====
+
+
+
+//流量统计用1像素图片地址,设置为空将不参与统计
+Recorder.TrafficImgUrl="//ia.51.la/go1?id=20469973&pvFlag=1";
+var Traffic=Recorder.Traffic=function(report){
+	if(!isBrowser)return;
+	report=report?"/"+RecTxt+"/Report/"+report:"";
+	var imgUrl=Recorder.TrafficImgUrl;
+	if(imgUrl){
+		var data=Recorder.Traffic;
+		var m=/^(https?:..[^\/#]*\/?)[^#]*/i.exec(location.href)||[];
+		var host=(m[1]||"http://file/");
+		var idf=(m[0]||host)+report;
+		
+		if(imgUrl.indexOf("//")==0){
+			//给url加上http前缀,如果是file协议下,不加前缀没法用
+			if(/^https:/i.test(idf)){
+				imgUrl="https:"+imgUrl;
+			}else{
+				imgUrl="http:"+imgUrl;
+			};
+		};
+		if(report){
+			imgUrl=imgUrl+"&cu="+encodeURIComponent(host+report);
+		};
+		
+		if(!data[idf]){
+			data[idf]=1;
+			
+			var img=new Image();
+			img.src=imgUrl;
+			CLog("Traffic Analysis Image: "+(report||RecTxt+".TrafficImgUrl="+Recorder.TrafficImgUrl));
+		};
+	};
+};
+
+
+
+if(WRec2){
+	CLog($T("8HO5::覆盖导入{1}",0,RecTxt),1);
+	WRec2.Destroy();
+};
+Export[RecTxt]=Recorder;
+
+}));

BIN
vendor/.DS_Store


BIN
vendor/bootstrap/.DS_Store


+ 4177 - 0
vendor/bootstrap/scss/bootstrap-grid.css

@@ -0,0 +1,4177 @@
+/*!
+ * Bootstrap Grid v4.6.0 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors
+ * Copyright 2011-2021 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+html {
+  -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+  -ms-overflow-style: scrollbar;
+}
+
+*,
+*::before,
+*::after {
+  -webkit-box-sizing: inherit;
+          box-sizing: inherit;
+}
+
+.container,
+.container-fluid,
+.container-sm,
+.container-md,
+.container-lg,
+.container-xl {
+  width: 100%;
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+@media (min-width: 576px) {
+  .container, .container-sm {
+    max-width: 540px;
+  }
+}
+
+@media (min-width: 768px) {
+  .container, .container-sm, .container-md {
+    max-width: 720px;
+  }
+}
+
+@media (min-width: 992px) {
+  .container, .container-sm, .container-md, .container-lg {
+    max-width: 960px;
+  }
+}
+
+@media (min-width: 1200px) {
+  .container, .container-sm, .container-md, .container-lg, .container-xl {
+    max-width: 1140px;
+  }
+}
+
+.row {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+.no-gutters {
+  margin-right: 0;
+  margin-left: 0;
+}
+
+.no-gutters > .col,
+.no-gutters > [class*="col-"] {
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,
+.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,
+.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,
+.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,
+.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,
+.col-xl-auto {
+  position: relative;
+  width: 100%;
+  padding-right: 15px;
+  padding-left: 15px;
+}
+
+.col {
+  -ms-flex-preferred-size: 0;
+      flex-basis: 0;
+  -webkit-box-flex: 1;
+      -ms-flex-positive: 1;
+          flex-grow: 1;
+  max-width: 100%;
+}
+
+.row-cols-1 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 100%;
+          flex: 0 0 100%;
+  max-width: 100%;
+}
+
+.row-cols-2 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 50%;
+          flex: 0 0 50%;
+  max-width: 50%;
+}
+
+.row-cols-3 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 33.33333%;
+          flex: 0 0 33.33333%;
+  max-width: 33.33333%;
+}
+
+.row-cols-4 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 25%;
+          flex: 0 0 25%;
+  max-width: 25%;
+}
+
+.row-cols-5 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 20%;
+          flex: 0 0 20%;
+  max-width: 20%;
+}
+
+.row-cols-6 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 16.66667%;
+          flex: 0 0 16.66667%;
+  max-width: 16.66667%;
+}
+
+.col-auto {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 auto;
+          flex: 0 0 auto;
+  width: auto;
+  max-width: 100%;
+}
+
+.col-1 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 8.33333%;
+          flex: 0 0 8.33333%;
+  max-width: 8.33333%;
+}
+
+.col-2 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 16.66667%;
+          flex: 0 0 16.66667%;
+  max-width: 16.66667%;
+}
+
+.col-3 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 25%;
+          flex: 0 0 25%;
+  max-width: 25%;
+}
+
+.col-4 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 33.33333%;
+          flex: 0 0 33.33333%;
+  max-width: 33.33333%;
+}
+
+.col-5 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 41.66667%;
+          flex: 0 0 41.66667%;
+  max-width: 41.66667%;
+}
+
+.col-6 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 50%;
+          flex: 0 0 50%;
+  max-width: 50%;
+}
+
+.col-7 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 58.33333%;
+          flex: 0 0 58.33333%;
+  max-width: 58.33333%;
+}
+
+.col-8 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 66.66667%;
+          flex: 0 0 66.66667%;
+  max-width: 66.66667%;
+}
+
+.col-9 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 75%;
+          flex: 0 0 75%;
+  max-width: 75%;
+}
+
+.col-10 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 83.33333%;
+          flex: 0 0 83.33333%;
+  max-width: 83.33333%;
+}
+
+.col-11 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 91.66667%;
+          flex: 0 0 91.66667%;
+  max-width: 91.66667%;
+}
+
+.col-12 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 100%;
+          flex: 0 0 100%;
+  max-width: 100%;
+}
+
+.order-first {
+  -webkit-box-ordinal-group: 0;
+      -ms-flex-order: -1;
+          order: -1;
+}
+
+.order-last {
+  -webkit-box-ordinal-group: 14;
+      -ms-flex-order: 13;
+          order: 13;
+}
+
+.order-0 {
+  -webkit-box-ordinal-group: 1;
+      -ms-flex-order: 0;
+          order: 0;
+}
+
+.order-1 {
+  -webkit-box-ordinal-group: 2;
+      -ms-flex-order: 1;
+          order: 1;
+}
+
+.order-2 {
+  -webkit-box-ordinal-group: 3;
+      -ms-flex-order: 2;
+          order: 2;
+}
+
+.order-3 {
+  -webkit-box-ordinal-group: 4;
+      -ms-flex-order: 3;
+          order: 3;
+}
+
+.order-4 {
+  -webkit-box-ordinal-group: 5;
+      -ms-flex-order: 4;
+          order: 4;
+}
+
+.order-5 {
+  -webkit-box-ordinal-group: 6;
+      -ms-flex-order: 5;
+          order: 5;
+}
+
+.order-6 {
+  -webkit-box-ordinal-group: 7;
+      -ms-flex-order: 6;
+          order: 6;
+}
+
+.order-7 {
+  -webkit-box-ordinal-group: 8;
+      -ms-flex-order: 7;
+          order: 7;
+}
+
+.order-8 {
+  -webkit-box-ordinal-group: 9;
+      -ms-flex-order: 8;
+          order: 8;
+}
+
+.order-9 {
+  -webkit-box-ordinal-group: 10;
+      -ms-flex-order: 9;
+          order: 9;
+}
+
+.order-10 {
+  -webkit-box-ordinal-group: 11;
+      -ms-flex-order: 10;
+          order: 10;
+}
+
+.order-11 {
+  -webkit-box-ordinal-group: 12;
+      -ms-flex-order: 11;
+          order: 11;
+}
+
+.order-12 {
+  -webkit-box-ordinal-group: 13;
+      -ms-flex-order: 12;
+          order: 12;
+}
+
+.offset-1 {
+  margin-left: 8.33333%;
+}
+
+.offset-2 {
+  margin-left: 16.66667%;
+}
+
+.offset-3 {
+  margin-left: 25%;
+}
+
+.offset-4 {
+  margin-left: 33.33333%;
+}
+
+.offset-5 {
+  margin-left: 41.66667%;
+}
+
+.offset-6 {
+  margin-left: 50%;
+}
+
+.offset-7 {
+  margin-left: 58.33333%;
+}
+
+.offset-8 {
+  margin-left: 66.66667%;
+}
+
+.offset-9 {
+  margin-left: 75%;
+}
+
+.offset-10 {
+  margin-left: 83.33333%;
+}
+
+.offset-11 {
+  margin-left: 91.66667%;
+}
+
+@media (min-width: 576px) {
+  .col-sm {
+    -ms-flex-preferred-size: 0;
+        flex-basis: 0;
+    -webkit-box-flex: 1;
+        -ms-flex-positive: 1;
+            flex-grow: 1;
+    max-width: 100%;
+  }
+  .row-cols-sm-1 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .row-cols-sm-2 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .row-cols-sm-3 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .row-cols-sm-4 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .row-cols-sm-5 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 20%;
+            flex: 0 0 20%;
+    max-width: 20%;
+  }
+  .row-cols-sm-6 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-sm-auto {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 auto;
+            flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+  .col-sm-1 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 8.33333%;
+            flex: 0 0 8.33333%;
+    max-width: 8.33333%;
+  }
+  .col-sm-2 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-sm-3 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .col-sm-4 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .col-sm-5 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 41.66667%;
+            flex: 0 0 41.66667%;
+    max-width: 41.66667%;
+  }
+  .col-sm-6 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .col-sm-7 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 58.33333%;
+            flex: 0 0 58.33333%;
+    max-width: 58.33333%;
+  }
+  .col-sm-8 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 66.66667%;
+            flex: 0 0 66.66667%;
+    max-width: 66.66667%;
+  }
+  .col-sm-9 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 75%;
+            flex: 0 0 75%;
+    max-width: 75%;
+  }
+  .col-sm-10 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 83.33333%;
+            flex: 0 0 83.33333%;
+    max-width: 83.33333%;
+  }
+  .col-sm-11 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 91.66667%;
+            flex: 0 0 91.66667%;
+    max-width: 91.66667%;
+  }
+  .col-sm-12 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .order-sm-first {
+    -webkit-box-ordinal-group: 0;
+        -ms-flex-order: -1;
+            order: -1;
+  }
+  .order-sm-last {
+    -webkit-box-ordinal-group: 14;
+        -ms-flex-order: 13;
+            order: 13;
+  }
+  .order-sm-0 {
+    -webkit-box-ordinal-group: 1;
+        -ms-flex-order: 0;
+            order: 0;
+  }
+  .order-sm-1 {
+    -webkit-box-ordinal-group: 2;
+        -ms-flex-order: 1;
+            order: 1;
+  }
+  .order-sm-2 {
+    -webkit-box-ordinal-group: 3;
+        -ms-flex-order: 2;
+            order: 2;
+  }
+  .order-sm-3 {
+    -webkit-box-ordinal-group: 4;
+        -ms-flex-order: 3;
+            order: 3;
+  }
+  .order-sm-4 {
+    -webkit-box-ordinal-group: 5;
+        -ms-flex-order: 4;
+            order: 4;
+  }
+  .order-sm-5 {
+    -webkit-box-ordinal-group: 6;
+        -ms-flex-order: 5;
+            order: 5;
+  }
+  .order-sm-6 {
+    -webkit-box-ordinal-group: 7;
+        -ms-flex-order: 6;
+            order: 6;
+  }
+  .order-sm-7 {
+    -webkit-box-ordinal-group: 8;
+        -ms-flex-order: 7;
+            order: 7;
+  }
+  .order-sm-8 {
+    -webkit-box-ordinal-group: 9;
+        -ms-flex-order: 8;
+            order: 8;
+  }
+  .order-sm-9 {
+    -webkit-box-ordinal-group: 10;
+        -ms-flex-order: 9;
+            order: 9;
+  }
+  .order-sm-10 {
+    -webkit-box-ordinal-group: 11;
+        -ms-flex-order: 10;
+            order: 10;
+  }
+  .order-sm-11 {
+    -webkit-box-ordinal-group: 12;
+        -ms-flex-order: 11;
+            order: 11;
+  }
+  .order-sm-12 {
+    -webkit-box-ordinal-group: 13;
+        -ms-flex-order: 12;
+            order: 12;
+  }
+  .offset-sm-0 {
+    margin-left: 0;
+  }
+  .offset-sm-1 {
+    margin-left: 8.33333%;
+  }
+  .offset-sm-2 {
+    margin-left: 16.66667%;
+  }
+  .offset-sm-3 {
+    margin-left: 25%;
+  }
+  .offset-sm-4 {
+    margin-left: 33.33333%;
+  }
+  .offset-sm-5 {
+    margin-left: 41.66667%;
+  }
+  .offset-sm-6 {
+    margin-left: 50%;
+  }
+  .offset-sm-7 {
+    margin-left: 58.33333%;
+  }
+  .offset-sm-8 {
+    margin-left: 66.66667%;
+  }
+  .offset-sm-9 {
+    margin-left: 75%;
+  }
+  .offset-sm-10 {
+    margin-left: 83.33333%;
+  }
+  .offset-sm-11 {
+    margin-left: 91.66667%;
+  }
+}
+
+@media (min-width: 768px) {
+  .col-md {
+    -ms-flex-preferred-size: 0;
+        flex-basis: 0;
+    -webkit-box-flex: 1;
+        -ms-flex-positive: 1;
+            flex-grow: 1;
+    max-width: 100%;
+  }
+  .row-cols-md-1 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .row-cols-md-2 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .row-cols-md-3 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .row-cols-md-4 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .row-cols-md-5 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 20%;
+            flex: 0 0 20%;
+    max-width: 20%;
+  }
+  .row-cols-md-6 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-md-auto {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 auto;
+            flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+  .col-md-1 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 8.33333%;
+            flex: 0 0 8.33333%;
+    max-width: 8.33333%;
+  }
+  .col-md-2 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-md-3 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .col-md-4 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .col-md-5 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 41.66667%;
+            flex: 0 0 41.66667%;
+    max-width: 41.66667%;
+  }
+  .col-md-6 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .col-md-7 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 58.33333%;
+            flex: 0 0 58.33333%;
+    max-width: 58.33333%;
+  }
+  .col-md-8 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 66.66667%;
+            flex: 0 0 66.66667%;
+    max-width: 66.66667%;
+  }
+  .col-md-9 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 75%;
+            flex: 0 0 75%;
+    max-width: 75%;
+  }
+  .col-md-10 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 83.33333%;
+            flex: 0 0 83.33333%;
+    max-width: 83.33333%;
+  }
+  .col-md-11 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 91.66667%;
+            flex: 0 0 91.66667%;
+    max-width: 91.66667%;
+  }
+  .col-md-12 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .order-md-first {
+    -webkit-box-ordinal-group: 0;
+        -ms-flex-order: -1;
+            order: -1;
+  }
+  .order-md-last {
+    -webkit-box-ordinal-group: 14;
+        -ms-flex-order: 13;
+            order: 13;
+  }
+  .order-md-0 {
+    -webkit-box-ordinal-group: 1;
+        -ms-flex-order: 0;
+            order: 0;
+  }
+  .order-md-1 {
+    -webkit-box-ordinal-group: 2;
+        -ms-flex-order: 1;
+            order: 1;
+  }
+  .order-md-2 {
+    -webkit-box-ordinal-group: 3;
+        -ms-flex-order: 2;
+            order: 2;
+  }
+  .order-md-3 {
+    -webkit-box-ordinal-group: 4;
+        -ms-flex-order: 3;
+            order: 3;
+  }
+  .order-md-4 {
+    -webkit-box-ordinal-group: 5;
+        -ms-flex-order: 4;
+            order: 4;
+  }
+  .order-md-5 {
+    -webkit-box-ordinal-group: 6;
+        -ms-flex-order: 5;
+            order: 5;
+  }
+  .order-md-6 {
+    -webkit-box-ordinal-group: 7;
+        -ms-flex-order: 6;
+            order: 6;
+  }
+  .order-md-7 {
+    -webkit-box-ordinal-group: 8;
+        -ms-flex-order: 7;
+            order: 7;
+  }
+  .order-md-8 {
+    -webkit-box-ordinal-group: 9;
+        -ms-flex-order: 8;
+            order: 8;
+  }
+  .order-md-9 {
+    -webkit-box-ordinal-group: 10;
+        -ms-flex-order: 9;
+            order: 9;
+  }
+  .order-md-10 {
+    -webkit-box-ordinal-group: 11;
+        -ms-flex-order: 10;
+            order: 10;
+  }
+  .order-md-11 {
+    -webkit-box-ordinal-group: 12;
+        -ms-flex-order: 11;
+            order: 11;
+  }
+  .order-md-12 {
+    -webkit-box-ordinal-group: 13;
+        -ms-flex-order: 12;
+            order: 12;
+  }
+  .offset-md-0 {
+    margin-left: 0;
+  }
+  .offset-md-1 {
+    margin-left: 8.33333%;
+  }
+  .offset-md-2 {
+    margin-left: 16.66667%;
+  }
+  .offset-md-3 {
+    margin-left: 25%;
+  }
+  .offset-md-4 {
+    margin-left: 33.33333%;
+  }
+  .offset-md-5 {
+    margin-left: 41.66667%;
+  }
+  .offset-md-6 {
+    margin-left: 50%;
+  }
+  .offset-md-7 {
+    margin-left: 58.33333%;
+  }
+  .offset-md-8 {
+    margin-left: 66.66667%;
+  }
+  .offset-md-9 {
+    margin-left: 75%;
+  }
+  .offset-md-10 {
+    margin-left: 83.33333%;
+  }
+  .offset-md-11 {
+    margin-left: 91.66667%;
+  }
+}
+
+@media (min-width: 992px) {
+  .col-lg {
+    -ms-flex-preferred-size: 0;
+        flex-basis: 0;
+    -webkit-box-flex: 1;
+        -ms-flex-positive: 1;
+            flex-grow: 1;
+    max-width: 100%;
+  }
+  .row-cols-lg-1 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .row-cols-lg-2 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .row-cols-lg-3 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .row-cols-lg-4 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .row-cols-lg-5 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 20%;
+            flex: 0 0 20%;
+    max-width: 20%;
+  }
+  .row-cols-lg-6 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-lg-auto {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 auto;
+            flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+  .col-lg-1 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 8.33333%;
+            flex: 0 0 8.33333%;
+    max-width: 8.33333%;
+  }
+  .col-lg-2 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-lg-3 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .col-lg-4 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .col-lg-5 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 41.66667%;
+            flex: 0 0 41.66667%;
+    max-width: 41.66667%;
+  }
+  .col-lg-6 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .col-lg-7 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 58.33333%;
+            flex: 0 0 58.33333%;
+    max-width: 58.33333%;
+  }
+  .col-lg-8 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 66.66667%;
+            flex: 0 0 66.66667%;
+    max-width: 66.66667%;
+  }
+  .col-lg-9 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 75%;
+            flex: 0 0 75%;
+    max-width: 75%;
+  }
+  .col-lg-10 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 83.33333%;
+            flex: 0 0 83.33333%;
+    max-width: 83.33333%;
+  }
+  .col-lg-11 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 91.66667%;
+            flex: 0 0 91.66667%;
+    max-width: 91.66667%;
+  }
+  .col-lg-12 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .order-lg-first {
+    -webkit-box-ordinal-group: 0;
+        -ms-flex-order: -1;
+            order: -1;
+  }
+  .order-lg-last {
+    -webkit-box-ordinal-group: 14;
+        -ms-flex-order: 13;
+            order: 13;
+  }
+  .order-lg-0 {
+    -webkit-box-ordinal-group: 1;
+        -ms-flex-order: 0;
+            order: 0;
+  }
+  .order-lg-1 {
+    -webkit-box-ordinal-group: 2;
+        -ms-flex-order: 1;
+            order: 1;
+  }
+  .order-lg-2 {
+    -webkit-box-ordinal-group: 3;
+        -ms-flex-order: 2;
+            order: 2;
+  }
+  .order-lg-3 {
+    -webkit-box-ordinal-group: 4;
+        -ms-flex-order: 3;
+            order: 3;
+  }
+  .order-lg-4 {
+    -webkit-box-ordinal-group: 5;
+        -ms-flex-order: 4;
+            order: 4;
+  }
+  .order-lg-5 {
+    -webkit-box-ordinal-group: 6;
+        -ms-flex-order: 5;
+            order: 5;
+  }
+  .order-lg-6 {
+    -webkit-box-ordinal-group: 7;
+        -ms-flex-order: 6;
+            order: 6;
+  }
+  .order-lg-7 {
+    -webkit-box-ordinal-group: 8;
+        -ms-flex-order: 7;
+            order: 7;
+  }
+  .order-lg-8 {
+    -webkit-box-ordinal-group: 9;
+        -ms-flex-order: 8;
+            order: 8;
+  }
+  .order-lg-9 {
+    -webkit-box-ordinal-group: 10;
+        -ms-flex-order: 9;
+            order: 9;
+  }
+  .order-lg-10 {
+    -webkit-box-ordinal-group: 11;
+        -ms-flex-order: 10;
+            order: 10;
+  }
+  .order-lg-11 {
+    -webkit-box-ordinal-group: 12;
+        -ms-flex-order: 11;
+            order: 11;
+  }
+  .order-lg-12 {
+    -webkit-box-ordinal-group: 13;
+        -ms-flex-order: 12;
+            order: 12;
+  }
+  .offset-lg-0 {
+    margin-left: 0;
+  }
+  .offset-lg-1 {
+    margin-left: 8.33333%;
+  }
+  .offset-lg-2 {
+    margin-left: 16.66667%;
+  }
+  .offset-lg-3 {
+    margin-left: 25%;
+  }
+  .offset-lg-4 {
+    margin-left: 33.33333%;
+  }
+  .offset-lg-5 {
+    margin-left: 41.66667%;
+  }
+  .offset-lg-6 {
+    margin-left: 50%;
+  }
+  .offset-lg-7 {
+    margin-left: 58.33333%;
+  }
+  .offset-lg-8 {
+    margin-left: 66.66667%;
+  }
+  .offset-lg-9 {
+    margin-left: 75%;
+  }
+  .offset-lg-10 {
+    margin-left: 83.33333%;
+  }
+  .offset-lg-11 {
+    margin-left: 91.66667%;
+  }
+}
+
+@media (min-width: 1200px) {
+  .col-xl {
+    -ms-flex-preferred-size: 0;
+        flex-basis: 0;
+    -webkit-box-flex: 1;
+        -ms-flex-positive: 1;
+            flex-grow: 1;
+    max-width: 100%;
+  }
+  .row-cols-xl-1 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .row-cols-xl-2 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .row-cols-xl-3 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .row-cols-xl-4 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .row-cols-xl-5 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 20%;
+            flex: 0 0 20%;
+    max-width: 20%;
+  }
+  .row-cols-xl-6 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-xl-auto {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 auto;
+            flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+  .col-xl-1 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 8.33333%;
+            flex: 0 0 8.33333%;
+    max-width: 8.33333%;
+  }
+  .col-xl-2 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-xl-3 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .col-xl-4 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .col-xl-5 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 41.66667%;
+            flex: 0 0 41.66667%;
+    max-width: 41.66667%;
+  }
+  .col-xl-6 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .col-xl-7 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 58.33333%;
+            flex: 0 0 58.33333%;
+    max-width: 58.33333%;
+  }
+  .col-xl-8 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 66.66667%;
+            flex: 0 0 66.66667%;
+    max-width: 66.66667%;
+  }
+  .col-xl-9 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 75%;
+            flex: 0 0 75%;
+    max-width: 75%;
+  }
+  .col-xl-10 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 83.33333%;
+            flex: 0 0 83.33333%;
+    max-width: 83.33333%;
+  }
+  .col-xl-11 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 91.66667%;
+            flex: 0 0 91.66667%;
+    max-width: 91.66667%;
+  }
+  .col-xl-12 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .order-xl-first {
+    -webkit-box-ordinal-group: 0;
+        -ms-flex-order: -1;
+            order: -1;
+  }
+  .order-xl-last {
+    -webkit-box-ordinal-group: 14;
+        -ms-flex-order: 13;
+            order: 13;
+  }
+  .order-xl-0 {
+    -webkit-box-ordinal-group: 1;
+        -ms-flex-order: 0;
+            order: 0;
+  }
+  .order-xl-1 {
+    -webkit-box-ordinal-group: 2;
+        -ms-flex-order: 1;
+            order: 1;
+  }
+  .order-xl-2 {
+    -webkit-box-ordinal-group: 3;
+        -ms-flex-order: 2;
+            order: 2;
+  }
+  .order-xl-3 {
+    -webkit-box-ordinal-group: 4;
+        -ms-flex-order: 3;
+            order: 3;
+  }
+  .order-xl-4 {
+    -webkit-box-ordinal-group: 5;
+        -ms-flex-order: 4;
+            order: 4;
+  }
+  .order-xl-5 {
+    -webkit-box-ordinal-group: 6;
+        -ms-flex-order: 5;
+            order: 5;
+  }
+  .order-xl-6 {
+    -webkit-box-ordinal-group: 7;
+        -ms-flex-order: 6;
+            order: 6;
+  }
+  .order-xl-7 {
+    -webkit-box-ordinal-group: 8;
+        -ms-flex-order: 7;
+            order: 7;
+  }
+  .order-xl-8 {
+    -webkit-box-ordinal-group: 9;
+        -ms-flex-order: 8;
+            order: 8;
+  }
+  .order-xl-9 {
+    -webkit-box-ordinal-group: 10;
+        -ms-flex-order: 9;
+            order: 9;
+  }
+  .order-xl-10 {
+    -webkit-box-ordinal-group: 11;
+        -ms-flex-order: 10;
+            order: 10;
+  }
+  .order-xl-11 {
+    -webkit-box-ordinal-group: 12;
+        -ms-flex-order: 11;
+            order: 11;
+  }
+  .order-xl-12 {
+    -webkit-box-ordinal-group: 13;
+        -ms-flex-order: 12;
+            order: 12;
+  }
+  .offset-xl-0 {
+    margin-left: 0;
+  }
+  .offset-xl-1 {
+    margin-left: 8.33333%;
+  }
+  .offset-xl-2 {
+    margin-left: 16.66667%;
+  }
+  .offset-xl-3 {
+    margin-left: 25%;
+  }
+  .offset-xl-4 {
+    margin-left: 33.33333%;
+  }
+  .offset-xl-5 {
+    margin-left: 41.66667%;
+  }
+  .offset-xl-6 {
+    margin-left: 50%;
+  }
+  .offset-xl-7 {
+    margin-left: 58.33333%;
+  }
+  .offset-xl-8 {
+    margin-left: 66.66667%;
+  }
+  .offset-xl-9 {
+    margin-left: 75%;
+  }
+  .offset-xl-10 {
+    margin-left: 83.33333%;
+  }
+  .offset-xl-11 {
+    margin-left: 91.66667%;
+  }
+}
+
+.d-none {
+  display: none !important;
+}
+
+.d-inline {
+  display: inline !important;
+}
+
+.d-inline-block {
+  display: inline-block !important;
+}
+
+.d-block {
+  display: block !important;
+}
+
+.d-table {
+  display: table !important;
+}
+
+.d-table-row {
+  display: table-row !important;
+}
+
+.d-table-cell {
+  display: table-cell !important;
+}
+
+.d-flex {
+  display: -webkit-box !important;
+  display: -ms-flexbox !important;
+  display: flex !important;
+}
+
+.d-inline-flex {
+  display: -webkit-inline-box !important;
+  display: -ms-inline-flexbox !important;
+  display: inline-flex !important;
+}
+
+@media (min-width: 576px) {
+  .d-sm-none {
+    display: none !important;
+  }
+  .d-sm-inline {
+    display: inline !important;
+  }
+  .d-sm-inline-block {
+    display: inline-block !important;
+  }
+  .d-sm-block {
+    display: block !important;
+  }
+  .d-sm-table {
+    display: table !important;
+  }
+  .d-sm-table-row {
+    display: table-row !important;
+  }
+  .d-sm-table-cell {
+    display: table-cell !important;
+  }
+  .d-sm-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-sm-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+@media (min-width: 768px) {
+  .d-md-none {
+    display: none !important;
+  }
+  .d-md-inline {
+    display: inline !important;
+  }
+  .d-md-inline-block {
+    display: inline-block !important;
+  }
+  .d-md-block {
+    display: block !important;
+  }
+  .d-md-table {
+    display: table !important;
+  }
+  .d-md-table-row {
+    display: table-row !important;
+  }
+  .d-md-table-cell {
+    display: table-cell !important;
+  }
+  .d-md-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-md-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .d-lg-none {
+    display: none !important;
+  }
+  .d-lg-inline {
+    display: inline !important;
+  }
+  .d-lg-inline-block {
+    display: inline-block !important;
+  }
+  .d-lg-block {
+    display: block !important;
+  }
+  .d-lg-table {
+    display: table !important;
+  }
+  .d-lg-table-row {
+    display: table-row !important;
+  }
+  .d-lg-table-cell {
+    display: table-cell !important;
+  }
+  .d-lg-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-lg-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .d-xl-none {
+    display: none !important;
+  }
+  .d-xl-inline {
+    display: inline !important;
+  }
+  .d-xl-inline-block {
+    display: inline-block !important;
+  }
+  .d-xl-block {
+    display: block !important;
+  }
+  .d-xl-table {
+    display: table !important;
+  }
+  .d-xl-table-row {
+    display: table-row !important;
+  }
+  .d-xl-table-cell {
+    display: table-cell !important;
+  }
+  .d-xl-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-xl-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+@media print {
+  .d-print-none {
+    display: none !important;
+  }
+  .d-print-inline {
+    display: inline !important;
+  }
+  .d-print-inline-block {
+    display: inline-block !important;
+  }
+  .d-print-block {
+    display: block !important;
+  }
+  .d-print-table {
+    display: table !important;
+  }
+  .d-print-table-row {
+    display: table-row !important;
+  }
+  .d-print-table-cell {
+    display: table-cell !important;
+  }
+  .d-print-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-print-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+.flex-row {
+  -webkit-box-orient: horizontal !important;
+  -webkit-box-direction: normal !important;
+      -ms-flex-direction: row !important;
+          flex-direction: row !important;
+}
+
+.flex-column {
+  -webkit-box-orient: vertical !important;
+  -webkit-box-direction: normal !important;
+      -ms-flex-direction: column !important;
+          flex-direction: column !important;
+}
+
+.flex-row-reverse {
+  -webkit-box-orient: horizontal !important;
+  -webkit-box-direction: reverse !important;
+      -ms-flex-direction: row-reverse !important;
+          flex-direction: row-reverse !important;
+}
+
+.flex-column-reverse {
+  -webkit-box-orient: vertical !important;
+  -webkit-box-direction: reverse !important;
+      -ms-flex-direction: column-reverse !important;
+          flex-direction: column-reverse !important;
+}
+
+.flex-wrap {
+  -ms-flex-wrap: wrap !important;
+      flex-wrap: wrap !important;
+}
+
+.flex-nowrap {
+  -ms-flex-wrap: nowrap !important;
+      flex-wrap: nowrap !important;
+}
+
+.flex-wrap-reverse {
+  -ms-flex-wrap: wrap-reverse !important;
+      flex-wrap: wrap-reverse !important;
+}
+
+.flex-fill {
+  -webkit-box-flex: 1 !important;
+      -ms-flex: 1 1 auto !important;
+          flex: 1 1 auto !important;
+}
+
+.flex-grow-0 {
+  -webkit-box-flex: 0 !important;
+      -ms-flex-positive: 0 !important;
+          flex-grow: 0 !important;
+}
+
+.flex-grow-1 {
+  -webkit-box-flex: 1 !important;
+      -ms-flex-positive: 1 !important;
+          flex-grow: 1 !important;
+}
+
+.flex-shrink-0 {
+  -ms-flex-negative: 0 !important;
+      flex-shrink: 0 !important;
+}
+
+.flex-shrink-1 {
+  -ms-flex-negative: 1 !important;
+      flex-shrink: 1 !important;
+}
+
+.justify-content-start {
+  -webkit-box-pack: start !important;
+      -ms-flex-pack: start !important;
+          justify-content: flex-start !important;
+}
+
+.justify-content-end {
+  -webkit-box-pack: end !important;
+      -ms-flex-pack: end !important;
+          justify-content: flex-end !important;
+}
+
+.justify-content-center {
+  -webkit-box-pack: center !important;
+      -ms-flex-pack: center !important;
+          justify-content: center !important;
+}
+
+.justify-content-between {
+  -webkit-box-pack: justify !important;
+      -ms-flex-pack: justify !important;
+          justify-content: space-between !important;
+}
+
+.justify-content-around {
+  -ms-flex-pack: distribute !important;
+      justify-content: space-around !important;
+}
+
+.align-items-start {
+  -webkit-box-align: start !important;
+      -ms-flex-align: start !important;
+          align-items: flex-start !important;
+}
+
+.align-items-end {
+  -webkit-box-align: end !important;
+      -ms-flex-align: end !important;
+          align-items: flex-end !important;
+}
+
+.align-items-center {
+  -webkit-box-align: center !important;
+      -ms-flex-align: center !important;
+          align-items: center !important;
+}
+
+.align-items-baseline {
+  -webkit-box-align: baseline !important;
+      -ms-flex-align: baseline !important;
+          align-items: baseline !important;
+}
+
+.align-items-stretch {
+  -webkit-box-align: stretch !important;
+      -ms-flex-align: stretch !important;
+          align-items: stretch !important;
+}
+
+.align-content-start {
+  -ms-flex-line-pack: start !important;
+      align-content: flex-start !important;
+}
+
+.align-content-end {
+  -ms-flex-line-pack: end !important;
+      align-content: flex-end !important;
+}
+
+.align-content-center {
+  -ms-flex-line-pack: center !important;
+      align-content: center !important;
+}
+
+.align-content-between {
+  -ms-flex-line-pack: justify !important;
+      align-content: space-between !important;
+}
+
+.align-content-around {
+  -ms-flex-line-pack: distribute !important;
+      align-content: space-around !important;
+}
+
+.align-content-stretch {
+  -ms-flex-line-pack: stretch !important;
+      align-content: stretch !important;
+}
+
+.align-self-auto {
+  -ms-flex-item-align: auto !important;
+      -ms-grid-row-align: auto !important;
+      align-self: auto !important;
+}
+
+.align-self-start {
+  -ms-flex-item-align: start !important;
+      align-self: flex-start !important;
+}
+
+.align-self-end {
+  -ms-flex-item-align: end !important;
+      align-self: flex-end !important;
+}
+
+.align-self-center {
+  -ms-flex-item-align: center !important;
+      -ms-grid-row-align: center !important;
+      align-self: center !important;
+}
+
+.align-self-baseline {
+  -ms-flex-item-align: baseline !important;
+      align-self: baseline !important;
+}
+
+.align-self-stretch {
+  -ms-flex-item-align: stretch !important;
+      -ms-grid-row-align: stretch !important;
+      align-self: stretch !important;
+}
+
+@media (min-width: 576px) {
+  .flex-sm-row {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: row !important;
+            flex-direction: row !important;
+  }
+  .flex-sm-column {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: column !important;
+            flex-direction: column !important;
+  }
+  .flex-sm-row-reverse {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: row-reverse !important;
+            flex-direction: row-reverse !important;
+  }
+  .flex-sm-column-reverse {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: column-reverse !important;
+            flex-direction: column-reverse !important;
+  }
+  .flex-sm-wrap {
+    -ms-flex-wrap: wrap !important;
+        flex-wrap: wrap !important;
+  }
+  .flex-sm-nowrap {
+    -ms-flex-wrap: nowrap !important;
+        flex-wrap: nowrap !important;
+  }
+  .flex-sm-wrap-reverse {
+    -ms-flex-wrap: wrap-reverse !important;
+        flex-wrap: wrap-reverse !important;
+  }
+  .flex-sm-fill {
+    -webkit-box-flex: 1 !important;
+        -ms-flex: 1 1 auto !important;
+            flex: 1 1 auto !important;
+  }
+  .flex-sm-grow-0 {
+    -webkit-box-flex: 0 !important;
+        -ms-flex-positive: 0 !important;
+            flex-grow: 0 !important;
+  }
+  .flex-sm-grow-1 {
+    -webkit-box-flex: 1 !important;
+        -ms-flex-positive: 1 !important;
+            flex-grow: 1 !important;
+  }
+  .flex-sm-shrink-0 {
+    -ms-flex-negative: 0 !important;
+        flex-shrink: 0 !important;
+  }
+  .flex-sm-shrink-1 {
+    -ms-flex-negative: 1 !important;
+        flex-shrink: 1 !important;
+  }
+  .justify-content-sm-start {
+    -webkit-box-pack: start !important;
+        -ms-flex-pack: start !important;
+            justify-content: flex-start !important;
+  }
+  .justify-content-sm-end {
+    -webkit-box-pack: end !important;
+        -ms-flex-pack: end !important;
+            justify-content: flex-end !important;
+  }
+  .justify-content-sm-center {
+    -webkit-box-pack: center !important;
+        -ms-flex-pack: center !important;
+            justify-content: center !important;
+  }
+  .justify-content-sm-between {
+    -webkit-box-pack: justify !important;
+        -ms-flex-pack: justify !important;
+            justify-content: space-between !important;
+  }
+  .justify-content-sm-around {
+    -ms-flex-pack: distribute !important;
+        justify-content: space-around !important;
+  }
+  .align-items-sm-start {
+    -webkit-box-align: start !important;
+        -ms-flex-align: start !important;
+            align-items: flex-start !important;
+  }
+  .align-items-sm-end {
+    -webkit-box-align: end !important;
+        -ms-flex-align: end !important;
+            align-items: flex-end !important;
+  }
+  .align-items-sm-center {
+    -webkit-box-align: center !important;
+        -ms-flex-align: center !important;
+            align-items: center !important;
+  }
+  .align-items-sm-baseline {
+    -webkit-box-align: baseline !important;
+        -ms-flex-align: baseline !important;
+            align-items: baseline !important;
+  }
+  .align-items-sm-stretch {
+    -webkit-box-align: stretch !important;
+        -ms-flex-align: stretch !important;
+            align-items: stretch !important;
+  }
+  .align-content-sm-start {
+    -ms-flex-line-pack: start !important;
+        align-content: flex-start !important;
+  }
+  .align-content-sm-end {
+    -ms-flex-line-pack: end !important;
+        align-content: flex-end !important;
+  }
+  .align-content-sm-center {
+    -ms-flex-line-pack: center !important;
+        align-content: center !important;
+  }
+  .align-content-sm-between {
+    -ms-flex-line-pack: justify !important;
+        align-content: space-between !important;
+  }
+  .align-content-sm-around {
+    -ms-flex-line-pack: distribute !important;
+        align-content: space-around !important;
+  }
+  .align-content-sm-stretch {
+    -ms-flex-line-pack: stretch !important;
+        align-content: stretch !important;
+  }
+  .align-self-sm-auto {
+    -ms-flex-item-align: auto !important;
+        -ms-grid-row-align: auto !important;
+        align-self: auto !important;
+  }
+  .align-self-sm-start {
+    -ms-flex-item-align: start !important;
+        align-self: flex-start !important;
+  }
+  .align-self-sm-end {
+    -ms-flex-item-align: end !important;
+        align-self: flex-end !important;
+  }
+  .align-self-sm-center {
+    -ms-flex-item-align: center !important;
+        -ms-grid-row-align: center !important;
+        align-self: center !important;
+  }
+  .align-self-sm-baseline {
+    -ms-flex-item-align: baseline !important;
+        align-self: baseline !important;
+  }
+  .align-self-sm-stretch {
+    -ms-flex-item-align: stretch !important;
+        -ms-grid-row-align: stretch !important;
+        align-self: stretch !important;
+  }
+}
+
+@media (min-width: 768px) {
+  .flex-md-row {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: row !important;
+            flex-direction: row !important;
+  }
+  .flex-md-column {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: column !important;
+            flex-direction: column !important;
+  }
+  .flex-md-row-reverse {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: row-reverse !important;
+            flex-direction: row-reverse !important;
+  }
+  .flex-md-column-reverse {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: column-reverse !important;
+            flex-direction: column-reverse !important;
+  }
+  .flex-md-wrap {
+    -ms-flex-wrap: wrap !important;
+        flex-wrap: wrap !important;
+  }
+  .flex-md-nowrap {
+    -ms-flex-wrap: nowrap !important;
+        flex-wrap: nowrap !important;
+  }
+  .flex-md-wrap-reverse {
+    -ms-flex-wrap: wrap-reverse !important;
+        flex-wrap: wrap-reverse !important;
+  }
+  .flex-md-fill {
+    -webkit-box-flex: 1 !important;
+        -ms-flex: 1 1 auto !important;
+            flex: 1 1 auto !important;
+  }
+  .flex-md-grow-0 {
+    -webkit-box-flex: 0 !important;
+        -ms-flex-positive: 0 !important;
+            flex-grow: 0 !important;
+  }
+  .flex-md-grow-1 {
+    -webkit-box-flex: 1 !important;
+        -ms-flex-positive: 1 !important;
+            flex-grow: 1 !important;
+  }
+  .flex-md-shrink-0 {
+    -ms-flex-negative: 0 !important;
+        flex-shrink: 0 !important;
+  }
+  .flex-md-shrink-1 {
+    -ms-flex-negative: 1 !important;
+        flex-shrink: 1 !important;
+  }
+  .justify-content-md-start {
+    -webkit-box-pack: start !important;
+        -ms-flex-pack: start !important;
+            justify-content: flex-start !important;
+  }
+  .justify-content-md-end {
+    -webkit-box-pack: end !important;
+        -ms-flex-pack: end !important;
+            justify-content: flex-end !important;
+  }
+  .justify-content-md-center {
+    -webkit-box-pack: center !important;
+        -ms-flex-pack: center !important;
+            justify-content: center !important;
+  }
+  .justify-content-md-between {
+    -webkit-box-pack: justify !important;
+        -ms-flex-pack: justify !important;
+            justify-content: space-between !important;
+  }
+  .justify-content-md-around {
+    -ms-flex-pack: distribute !important;
+        justify-content: space-around !important;
+  }
+  .align-items-md-start {
+    -webkit-box-align: start !important;
+        -ms-flex-align: start !important;
+            align-items: flex-start !important;
+  }
+  .align-items-md-end {
+    -webkit-box-align: end !important;
+        -ms-flex-align: end !important;
+            align-items: flex-end !important;
+  }
+  .align-items-md-center {
+    -webkit-box-align: center !important;
+        -ms-flex-align: center !important;
+            align-items: center !important;
+  }
+  .align-items-md-baseline {
+    -webkit-box-align: baseline !important;
+        -ms-flex-align: baseline !important;
+            align-items: baseline !important;
+  }
+  .align-items-md-stretch {
+    -webkit-box-align: stretch !important;
+        -ms-flex-align: stretch !important;
+            align-items: stretch !important;
+  }
+  .align-content-md-start {
+    -ms-flex-line-pack: start !important;
+        align-content: flex-start !important;
+  }
+  .align-content-md-end {
+    -ms-flex-line-pack: end !important;
+        align-content: flex-end !important;
+  }
+  .align-content-md-center {
+    -ms-flex-line-pack: center !important;
+        align-content: center !important;
+  }
+  .align-content-md-between {
+    -ms-flex-line-pack: justify !important;
+        align-content: space-between !important;
+  }
+  .align-content-md-around {
+    -ms-flex-line-pack: distribute !important;
+        align-content: space-around !important;
+  }
+  .align-content-md-stretch {
+    -ms-flex-line-pack: stretch !important;
+        align-content: stretch !important;
+  }
+  .align-self-md-auto {
+    -ms-flex-item-align: auto !important;
+        -ms-grid-row-align: auto !important;
+        align-self: auto !important;
+  }
+  .align-self-md-start {
+    -ms-flex-item-align: start !important;
+        align-self: flex-start !important;
+  }
+  .align-self-md-end {
+    -ms-flex-item-align: end !important;
+        align-self: flex-end !important;
+  }
+  .align-self-md-center {
+    -ms-flex-item-align: center !important;
+        -ms-grid-row-align: center !important;
+        align-self: center !important;
+  }
+  .align-self-md-baseline {
+    -ms-flex-item-align: baseline !important;
+        align-self: baseline !important;
+  }
+  .align-self-md-stretch {
+    -ms-flex-item-align: stretch !important;
+        -ms-grid-row-align: stretch !important;
+        align-self: stretch !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .flex-lg-row {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: row !important;
+            flex-direction: row !important;
+  }
+  .flex-lg-column {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: column !important;
+            flex-direction: column !important;
+  }
+  .flex-lg-row-reverse {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: row-reverse !important;
+            flex-direction: row-reverse !important;
+  }
+  .flex-lg-column-reverse {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: column-reverse !important;
+            flex-direction: column-reverse !important;
+  }
+  .flex-lg-wrap {
+    -ms-flex-wrap: wrap !important;
+        flex-wrap: wrap !important;
+  }
+  .flex-lg-nowrap {
+    -ms-flex-wrap: nowrap !important;
+        flex-wrap: nowrap !important;
+  }
+  .flex-lg-wrap-reverse {
+    -ms-flex-wrap: wrap-reverse !important;
+        flex-wrap: wrap-reverse !important;
+  }
+  .flex-lg-fill {
+    -webkit-box-flex: 1 !important;
+        -ms-flex: 1 1 auto !important;
+            flex: 1 1 auto !important;
+  }
+  .flex-lg-grow-0 {
+    -webkit-box-flex: 0 !important;
+        -ms-flex-positive: 0 !important;
+            flex-grow: 0 !important;
+  }
+  .flex-lg-grow-1 {
+    -webkit-box-flex: 1 !important;
+        -ms-flex-positive: 1 !important;
+            flex-grow: 1 !important;
+  }
+  .flex-lg-shrink-0 {
+    -ms-flex-negative: 0 !important;
+        flex-shrink: 0 !important;
+  }
+  .flex-lg-shrink-1 {
+    -ms-flex-negative: 1 !important;
+        flex-shrink: 1 !important;
+  }
+  .justify-content-lg-start {
+    -webkit-box-pack: start !important;
+        -ms-flex-pack: start !important;
+            justify-content: flex-start !important;
+  }
+  .justify-content-lg-end {
+    -webkit-box-pack: end !important;
+        -ms-flex-pack: end !important;
+            justify-content: flex-end !important;
+  }
+  .justify-content-lg-center {
+    -webkit-box-pack: center !important;
+        -ms-flex-pack: center !important;
+            justify-content: center !important;
+  }
+  .justify-content-lg-between {
+    -webkit-box-pack: justify !important;
+        -ms-flex-pack: justify !important;
+            justify-content: space-between !important;
+  }
+  .justify-content-lg-around {
+    -ms-flex-pack: distribute !important;
+        justify-content: space-around !important;
+  }
+  .align-items-lg-start {
+    -webkit-box-align: start !important;
+        -ms-flex-align: start !important;
+            align-items: flex-start !important;
+  }
+  .align-items-lg-end {
+    -webkit-box-align: end !important;
+        -ms-flex-align: end !important;
+            align-items: flex-end !important;
+  }
+  .align-items-lg-center {
+    -webkit-box-align: center !important;
+        -ms-flex-align: center !important;
+            align-items: center !important;
+  }
+  .align-items-lg-baseline {
+    -webkit-box-align: baseline !important;
+        -ms-flex-align: baseline !important;
+            align-items: baseline !important;
+  }
+  .align-items-lg-stretch {
+    -webkit-box-align: stretch !important;
+        -ms-flex-align: stretch !important;
+            align-items: stretch !important;
+  }
+  .align-content-lg-start {
+    -ms-flex-line-pack: start !important;
+        align-content: flex-start !important;
+  }
+  .align-content-lg-end {
+    -ms-flex-line-pack: end !important;
+        align-content: flex-end !important;
+  }
+  .align-content-lg-center {
+    -ms-flex-line-pack: center !important;
+        align-content: center !important;
+  }
+  .align-content-lg-between {
+    -ms-flex-line-pack: justify !important;
+        align-content: space-between !important;
+  }
+  .align-content-lg-around {
+    -ms-flex-line-pack: distribute !important;
+        align-content: space-around !important;
+  }
+  .align-content-lg-stretch {
+    -ms-flex-line-pack: stretch !important;
+        align-content: stretch !important;
+  }
+  .align-self-lg-auto {
+    -ms-flex-item-align: auto !important;
+        -ms-grid-row-align: auto !important;
+        align-self: auto !important;
+  }
+  .align-self-lg-start {
+    -ms-flex-item-align: start !important;
+        align-self: flex-start !important;
+  }
+  .align-self-lg-end {
+    -ms-flex-item-align: end !important;
+        align-self: flex-end !important;
+  }
+  .align-self-lg-center {
+    -ms-flex-item-align: center !important;
+        -ms-grid-row-align: center !important;
+        align-self: center !important;
+  }
+  .align-self-lg-baseline {
+    -ms-flex-item-align: baseline !important;
+        align-self: baseline !important;
+  }
+  .align-self-lg-stretch {
+    -ms-flex-item-align: stretch !important;
+        -ms-grid-row-align: stretch !important;
+        align-self: stretch !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .flex-xl-row {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: row !important;
+            flex-direction: row !important;
+  }
+  .flex-xl-column {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: column !important;
+            flex-direction: column !important;
+  }
+  .flex-xl-row-reverse {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: row-reverse !important;
+            flex-direction: row-reverse !important;
+  }
+  .flex-xl-column-reverse {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: column-reverse !important;
+            flex-direction: column-reverse !important;
+  }
+  .flex-xl-wrap {
+    -ms-flex-wrap: wrap !important;
+        flex-wrap: wrap !important;
+  }
+  .flex-xl-nowrap {
+    -ms-flex-wrap: nowrap !important;
+        flex-wrap: nowrap !important;
+  }
+  .flex-xl-wrap-reverse {
+    -ms-flex-wrap: wrap-reverse !important;
+        flex-wrap: wrap-reverse !important;
+  }
+  .flex-xl-fill {
+    -webkit-box-flex: 1 !important;
+        -ms-flex: 1 1 auto !important;
+            flex: 1 1 auto !important;
+  }
+  .flex-xl-grow-0 {
+    -webkit-box-flex: 0 !important;
+        -ms-flex-positive: 0 !important;
+            flex-grow: 0 !important;
+  }
+  .flex-xl-grow-1 {
+    -webkit-box-flex: 1 !important;
+        -ms-flex-positive: 1 !important;
+            flex-grow: 1 !important;
+  }
+  .flex-xl-shrink-0 {
+    -ms-flex-negative: 0 !important;
+        flex-shrink: 0 !important;
+  }
+  .flex-xl-shrink-1 {
+    -ms-flex-negative: 1 !important;
+        flex-shrink: 1 !important;
+  }
+  .justify-content-xl-start {
+    -webkit-box-pack: start !important;
+        -ms-flex-pack: start !important;
+            justify-content: flex-start !important;
+  }
+  .justify-content-xl-end {
+    -webkit-box-pack: end !important;
+        -ms-flex-pack: end !important;
+            justify-content: flex-end !important;
+  }
+  .justify-content-xl-center {
+    -webkit-box-pack: center !important;
+        -ms-flex-pack: center !important;
+            justify-content: center !important;
+  }
+  .justify-content-xl-between {
+    -webkit-box-pack: justify !important;
+        -ms-flex-pack: justify !important;
+            justify-content: space-between !important;
+  }
+  .justify-content-xl-around {
+    -ms-flex-pack: distribute !important;
+        justify-content: space-around !important;
+  }
+  .align-items-xl-start {
+    -webkit-box-align: start !important;
+        -ms-flex-align: start !important;
+            align-items: flex-start !important;
+  }
+  .align-items-xl-end {
+    -webkit-box-align: end !important;
+        -ms-flex-align: end !important;
+            align-items: flex-end !important;
+  }
+  .align-items-xl-center {
+    -webkit-box-align: center !important;
+        -ms-flex-align: center !important;
+            align-items: center !important;
+  }
+  .align-items-xl-baseline {
+    -webkit-box-align: baseline !important;
+        -ms-flex-align: baseline !important;
+            align-items: baseline !important;
+  }
+  .align-items-xl-stretch {
+    -webkit-box-align: stretch !important;
+        -ms-flex-align: stretch !important;
+            align-items: stretch !important;
+  }
+  .align-content-xl-start {
+    -ms-flex-line-pack: start !important;
+        align-content: flex-start !important;
+  }
+  .align-content-xl-end {
+    -ms-flex-line-pack: end !important;
+        align-content: flex-end !important;
+  }
+  .align-content-xl-center {
+    -ms-flex-line-pack: center !important;
+        align-content: center !important;
+  }
+  .align-content-xl-between {
+    -ms-flex-line-pack: justify !important;
+        align-content: space-between !important;
+  }
+  .align-content-xl-around {
+    -ms-flex-line-pack: distribute !important;
+        align-content: space-around !important;
+  }
+  .align-content-xl-stretch {
+    -ms-flex-line-pack: stretch !important;
+        align-content: stretch !important;
+  }
+  .align-self-xl-auto {
+    -ms-flex-item-align: auto !important;
+        -ms-grid-row-align: auto !important;
+        align-self: auto !important;
+  }
+  .align-self-xl-start {
+    -ms-flex-item-align: start !important;
+        align-self: flex-start !important;
+  }
+  .align-self-xl-end {
+    -ms-flex-item-align: end !important;
+        align-self: flex-end !important;
+  }
+  .align-self-xl-center {
+    -ms-flex-item-align: center !important;
+        -ms-grid-row-align: center !important;
+        align-self: center !important;
+  }
+  .align-self-xl-baseline {
+    -ms-flex-item-align: baseline !important;
+        align-self: baseline !important;
+  }
+  .align-self-xl-stretch {
+    -ms-flex-item-align: stretch !important;
+        -ms-grid-row-align: stretch !important;
+        align-self: stretch !important;
+  }
+}
+
+.m-0 {
+  margin: 0 !important;
+}
+
+.mt-0,
+.my-0 {
+  margin-top: 0 !important;
+}
+
+.mr-0,
+.mx-0 {
+  margin-right: 0 !important;
+}
+
+.mb-0,
+.my-0 {
+  margin-bottom: 0 !important;
+}
+
+.ml-0,
+.mx-0 {
+  margin-left: 0 !important;
+}
+
+.m-1 {
+  margin: 0.25rem !important;
+}
+
+.mt-1,
+.my-1 {
+  margin-top: 0.25rem !important;
+}
+
+.mr-1,
+.mx-1 {
+  margin-right: 0.25rem !important;
+}
+
+.mb-1,
+.my-1 {
+  margin-bottom: 0.25rem !important;
+}
+
+.ml-1,
+.mx-1 {
+  margin-left: 0.25rem !important;
+}
+
+.m-2 {
+  margin: 0.5rem !important;
+}
+
+.mt-2,
+.my-2 {
+  margin-top: 0.5rem !important;
+}
+
+.mr-2,
+.mx-2 {
+  margin-right: 0.5rem !important;
+}
+
+.mb-2,
+.my-2 {
+  margin-bottom: 0.5rem !important;
+}
+
+.ml-2,
+.mx-2 {
+  margin-left: 0.5rem !important;
+}
+
+.m-3 {
+  margin: 1rem !important;
+}
+
+.mt-3,
+.my-3 {
+  margin-top: 1rem !important;
+}
+
+.mr-3,
+.mx-3 {
+  margin-right: 1rem !important;
+}
+
+.mb-3,
+.my-3 {
+  margin-bottom: 1rem !important;
+}
+
+.ml-3,
+.mx-3 {
+  margin-left: 1rem !important;
+}
+
+.m-4 {
+  margin: 1.5rem !important;
+}
+
+.mt-4,
+.my-4 {
+  margin-top: 1.5rem !important;
+}
+
+.mr-4,
+.mx-4 {
+  margin-right: 1.5rem !important;
+}
+
+.mb-4,
+.my-4 {
+  margin-bottom: 1.5rem !important;
+}
+
+.ml-4,
+.mx-4 {
+  margin-left: 1.5rem !important;
+}
+
+.m-5 {
+  margin: 3rem !important;
+}
+
+.mt-5,
+.my-5 {
+  margin-top: 3rem !important;
+}
+
+.mr-5,
+.mx-5 {
+  margin-right: 3rem !important;
+}
+
+.mb-5,
+.my-5 {
+  margin-bottom: 3rem !important;
+}
+
+.ml-5,
+.mx-5 {
+  margin-left: 3rem !important;
+}
+
+.p-0 {
+  padding: 0 !important;
+}
+
+.pt-0,
+.py-0 {
+  padding-top: 0 !important;
+}
+
+.pr-0,
+.px-0 {
+  padding-right: 0 !important;
+}
+
+.pb-0,
+.py-0 {
+  padding-bottom: 0 !important;
+}
+
+.pl-0,
+.px-0 {
+  padding-left: 0 !important;
+}
+
+.p-1 {
+  padding: 0.25rem !important;
+}
+
+.pt-1,
+.py-1 {
+  padding-top: 0.25rem !important;
+}
+
+.pr-1,
+.px-1 {
+  padding-right: 0.25rem !important;
+}
+
+.pb-1,
+.py-1 {
+  padding-bottom: 0.25rem !important;
+}
+
+.pl-1,
+.px-1 {
+  padding-left: 0.25rem !important;
+}
+
+.p-2 {
+  padding: 0.5rem !important;
+}
+
+.pt-2,
+.py-2 {
+  padding-top: 0.5rem !important;
+}
+
+.pr-2,
+.px-2 {
+  padding-right: 0.5rem !important;
+}
+
+.pb-2,
+.py-2 {
+  padding-bottom: 0.5rem !important;
+}
+
+.pl-2,
+.px-2 {
+  padding-left: 0.5rem !important;
+}
+
+.p-3 {
+  padding: 1rem !important;
+}
+
+.pt-3,
+.py-3 {
+  padding-top: 1rem !important;
+}
+
+.pr-3,
+.px-3 {
+  padding-right: 1rem !important;
+}
+
+.pb-3,
+.py-3 {
+  padding-bottom: 1rem !important;
+}
+
+.pl-3,
+.px-3 {
+  padding-left: 1rem !important;
+}
+
+.p-4 {
+  padding: 1.5rem !important;
+}
+
+.pt-4,
+.py-4 {
+  padding-top: 1.5rem !important;
+}
+
+.pr-4,
+.px-4 {
+  padding-right: 1.5rem !important;
+}
+
+.pb-4,
+.py-4 {
+  padding-bottom: 1.5rem !important;
+}
+
+.pl-4,
+.px-4 {
+  padding-left: 1.5rem !important;
+}
+
+.p-5 {
+  padding: 3rem !important;
+}
+
+.pt-5,
+.py-5 {
+  padding-top: 3rem !important;
+}
+
+.pr-5,
+.px-5 {
+  padding-right: 3rem !important;
+}
+
+.pb-5,
+.py-5 {
+  padding-bottom: 3rem !important;
+}
+
+.pl-5,
+.px-5 {
+  padding-left: 3rem !important;
+}
+
+.m-n1 {
+  margin: -0.25rem !important;
+}
+
+.mt-n1,
+.my-n1 {
+  margin-top: -0.25rem !important;
+}
+
+.mr-n1,
+.mx-n1 {
+  margin-right: -0.25rem !important;
+}
+
+.mb-n1,
+.my-n1 {
+  margin-bottom: -0.25rem !important;
+}
+
+.ml-n1,
+.mx-n1 {
+  margin-left: -0.25rem !important;
+}
+
+.m-n2 {
+  margin: -0.5rem !important;
+}
+
+.mt-n2,
+.my-n2 {
+  margin-top: -0.5rem !important;
+}
+
+.mr-n2,
+.mx-n2 {
+  margin-right: -0.5rem !important;
+}
+
+.mb-n2,
+.my-n2 {
+  margin-bottom: -0.5rem !important;
+}
+
+.ml-n2,
+.mx-n2 {
+  margin-left: -0.5rem !important;
+}
+
+.m-n3 {
+  margin: -1rem !important;
+}
+
+.mt-n3,
+.my-n3 {
+  margin-top: -1rem !important;
+}
+
+.mr-n3,
+.mx-n3 {
+  margin-right: -1rem !important;
+}
+
+.mb-n3,
+.my-n3 {
+  margin-bottom: -1rem !important;
+}
+
+.ml-n3,
+.mx-n3 {
+  margin-left: -1rem !important;
+}
+
+.m-n4 {
+  margin: -1.5rem !important;
+}
+
+.mt-n4,
+.my-n4 {
+  margin-top: -1.5rem !important;
+}
+
+.mr-n4,
+.mx-n4 {
+  margin-right: -1.5rem !important;
+}
+
+.mb-n4,
+.my-n4 {
+  margin-bottom: -1.5rem !important;
+}
+
+.ml-n4,
+.mx-n4 {
+  margin-left: -1.5rem !important;
+}
+
+.m-n5 {
+  margin: -3rem !important;
+}
+
+.mt-n5,
+.my-n5 {
+  margin-top: -3rem !important;
+}
+
+.mr-n5,
+.mx-n5 {
+  margin-right: -3rem !important;
+}
+
+.mb-n5,
+.my-n5 {
+  margin-bottom: -3rem !important;
+}
+
+.ml-n5,
+.mx-n5 {
+  margin-left: -3rem !important;
+}
+
+.m-auto {
+  margin: auto !important;
+}
+
+.mt-auto,
+.my-auto {
+  margin-top: auto !important;
+}
+
+.mr-auto,
+.mx-auto {
+  margin-right: auto !important;
+}
+
+.mb-auto,
+.my-auto {
+  margin-bottom: auto !important;
+}
+
+.ml-auto,
+.mx-auto {
+  margin-left: auto !important;
+}
+
+@media (min-width: 576px) {
+  .m-sm-0 {
+    margin: 0 !important;
+  }
+  .mt-sm-0,
+  .my-sm-0 {
+    margin-top: 0 !important;
+  }
+  .mr-sm-0,
+  .mx-sm-0 {
+    margin-right: 0 !important;
+  }
+  .mb-sm-0,
+  .my-sm-0 {
+    margin-bottom: 0 !important;
+  }
+  .ml-sm-0,
+  .mx-sm-0 {
+    margin-left: 0 !important;
+  }
+  .m-sm-1 {
+    margin: 0.25rem !important;
+  }
+  .mt-sm-1,
+  .my-sm-1 {
+    margin-top: 0.25rem !important;
+  }
+  .mr-sm-1,
+  .mx-sm-1 {
+    margin-right: 0.25rem !important;
+  }
+  .mb-sm-1,
+  .my-sm-1 {
+    margin-bottom: 0.25rem !important;
+  }
+  .ml-sm-1,
+  .mx-sm-1 {
+    margin-left: 0.25rem !important;
+  }
+  .m-sm-2 {
+    margin: 0.5rem !important;
+  }
+  .mt-sm-2,
+  .my-sm-2 {
+    margin-top: 0.5rem !important;
+  }
+  .mr-sm-2,
+  .mx-sm-2 {
+    margin-right: 0.5rem !important;
+  }
+  .mb-sm-2,
+  .my-sm-2 {
+    margin-bottom: 0.5rem !important;
+  }
+  .ml-sm-2,
+  .mx-sm-2 {
+    margin-left: 0.5rem !important;
+  }
+  .m-sm-3 {
+    margin: 1rem !important;
+  }
+  .mt-sm-3,
+  .my-sm-3 {
+    margin-top: 1rem !important;
+  }
+  .mr-sm-3,
+  .mx-sm-3 {
+    margin-right: 1rem !important;
+  }
+  .mb-sm-3,
+  .my-sm-3 {
+    margin-bottom: 1rem !important;
+  }
+  .ml-sm-3,
+  .mx-sm-3 {
+    margin-left: 1rem !important;
+  }
+  .m-sm-4 {
+    margin: 1.5rem !important;
+  }
+  .mt-sm-4,
+  .my-sm-4 {
+    margin-top: 1.5rem !important;
+  }
+  .mr-sm-4,
+  .mx-sm-4 {
+    margin-right: 1.5rem !important;
+  }
+  .mb-sm-4,
+  .my-sm-4 {
+    margin-bottom: 1.5rem !important;
+  }
+  .ml-sm-4,
+  .mx-sm-4 {
+    margin-left: 1.5rem !important;
+  }
+  .m-sm-5 {
+    margin: 3rem !important;
+  }
+  .mt-sm-5,
+  .my-sm-5 {
+    margin-top: 3rem !important;
+  }
+  .mr-sm-5,
+  .mx-sm-5 {
+    margin-right: 3rem !important;
+  }
+  .mb-sm-5,
+  .my-sm-5 {
+    margin-bottom: 3rem !important;
+  }
+  .ml-sm-5,
+  .mx-sm-5 {
+    margin-left: 3rem !important;
+  }
+  .p-sm-0 {
+    padding: 0 !important;
+  }
+  .pt-sm-0,
+  .py-sm-0 {
+    padding-top: 0 !important;
+  }
+  .pr-sm-0,
+  .px-sm-0 {
+    padding-right: 0 !important;
+  }
+  .pb-sm-0,
+  .py-sm-0 {
+    padding-bottom: 0 !important;
+  }
+  .pl-sm-0,
+  .px-sm-0 {
+    padding-left: 0 !important;
+  }
+  .p-sm-1 {
+    padding: 0.25rem !important;
+  }
+  .pt-sm-1,
+  .py-sm-1 {
+    padding-top: 0.25rem !important;
+  }
+  .pr-sm-1,
+  .px-sm-1 {
+    padding-right: 0.25rem !important;
+  }
+  .pb-sm-1,
+  .py-sm-1 {
+    padding-bottom: 0.25rem !important;
+  }
+  .pl-sm-1,
+  .px-sm-1 {
+    padding-left: 0.25rem !important;
+  }
+  .p-sm-2 {
+    padding: 0.5rem !important;
+  }
+  .pt-sm-2,
+  .py-sm-2 {
+    padding-top: 0.5rem !important;
+  }
+  .pr-sm-2,
+  .px-sm-2 {
+    padding-right: 0.5rem !important;
+  }
+  .pb-sm-2,
+  .py-sm-2 {
+    padding-bottom: 0.5rem !important;
+  }
+  .pl-sm-2,
+  .px-sm-2 {
+    padding-left: 0.5rem !important;
+  }
+  .p-sm-3 {
+    padding: 1rem !important;
+  }
+  .pt-sm-3,
+  .py-sm-3 {
+    padding-top: 1rem !important;
+  }
+  .pr-sm-3,
+  .px-sm-3 {
+    padding-right: 1rem !important;
+  }
+  .pb-sm-3,
+  .py-sm-3 {
+    padding-bottom: 1rem !important;
+  }
+  .pl-sm-3,
+  .px-sm-3 {
+    padding-left: 1rem !important;
+  }
+  .p-sm-4 {
+    padding: 1.5rem !important;
+  }
+  .pt-sm-4,
+  .py-sm-4 {
+    padding-top: 1.5rem !important;
+  }
+  .pr-sm-4,
+  .px-sm-4 {
+    padding-right: 1.5rem !important;
+  }
+  .pb-sm-4,
+  .py-sm-4 {
+    padding-bottom: 1.5rem !important;
+  }
+  .pl-sm-4,
+  .px-sm-4 {
+    padding-left: 1.5rem !important;
+  }
+  .p-sm-5 {
+    padding: 3rem !important;
+  }
+  .pt-sm-5,
+  .py-sm-5 {
+    padding-top: 3rem !important;
+  }
+  .pr-sm-5,
+  .px-sm-5 {
+    padding-right: 3rem !important;
+  }
+  .pb-sm-5,
+  .py-sm-5 {
+    padding-bottom: 3rem !important;
+  }
+  .pl-sm-5,
+  .px-sm-5 {
+    padding-left: 3rem !important;
+  }
+  .m-sm-n1 {
+    margin: -0.25rem !important;
+  }
+  .mt-sm-n1,
+  .my-sm-n1 {
+    margin-top: -0.25rem !important;
+  }
+  .mr-sm-n1,
+  .mx-sm-n1 {
+    margin-right: -0.25rem !important;
+  }
+  .mb-sm-n1,
+  .my-sm-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+  .ml-sm-n1,
+  .mx-sm-n1 {
+    margin-left: -0.25rem !important;
+  }
+  .m-sm-n2 {
+    margin: -0.5rem !important;
+  }
+  .mt-sm-n2,
+  .my-sm-n2 {
+    margin-top: -0.5rem !important;
+  }
+  .mr-sm-n2,
+  .mx-sm-n2 {
+    margin-right: -0.5rem !important;
+  }
+  .mb-sm-n2,
+  .my-sm-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+  .ml-sm-n2,
+  .mx-sm-n2 {
+    margin-left: -0.5rem !important;
+  }
+  .m-sm-n3 {
+    margin: -1rem !important;
+  }
+  .mt-sm-n3,
+  .my-sm-n3 {
+    margin-top: -1rem !important;
+  }
+  .mr-sm-n3,
+  .mx-sm-n3 {
+    margin-right: -1rem !important;
+  }
+  .mb-sm-n3,
+  .my-sm-n3 {
+    margin-bottom: -1rem !important;
+  }
+  .ml-sm-n3,
+  .mx-sm-n3 {
+    margin-left: -1rem !important;
+  }
+  .m-sm-n4 {
+    margin: -1.5rem !important;
+  }
+  .mt-sm-n4,
+  .my-sm-n4 {
+    margin-top: -1.5rem !important;
+  }
+  .mr-sm-n4,
+  .mx-sm-n4 {
+    margin-right: -1.5rem !important;
+  }
+  .mb-sm-n4,
+  .my-sm-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+  .ml-sm-n4,
+  .mx-sm-n4 {
+    margin-left: -1.5rem !important;
+  }
+  .m-sm-n5 {
+    margin: -3rem !important;
+  }
+  .mt-sm-n5,
+  .my-sm-n5 {
+    margin-top: -3rem !important;
+  }
+  .mr-sm-n5,
+  .mx-sm-n5 {
+    margin-right: -3rem !important;
+  }
+  .mb-sm-n5,
+  .my-sm-n5 {
+    margin-bottom: -3rem !important;
+  }
+  .ml-sm-n5,
+  .mx-sm-n5 {
+    margin-left: -3rem !important;
+  }
+  .m-sm-auto {
+    margin: auto !important;
+  }
+  .mt-sm-auto,
+  .my-sm-auto {
+    margin-top: auto !important;
+  }
+  .mr-sm-auto,
+  .mx-sm-auto {
+    margin-right: auto !important;
+  }
+  .mb-sm-auto,
+  .my-sm-auto {
+    margin-bottom: auto !important;
+  }
+  .ml-sm-auto,
+  .mx-sm-auto {
+    margin-left: auto !important;
+  }
+}
+
+@media (min-width: 768px) {
+  .m-md-0 {
+    margin: 0 !important;
+  }
+  .mt-md-0,
+  .my-md-0 {
+    margin-top: 0 !important;
+  }
+  .mr-md-0,
+  .mx-md-0 {
+    margin-right: 0 !important;
+  }
+  .mb-md-0,
+  .my-md-0 {
+    margin-bottom: 0 !important;
+  }
+  .ml-md-0,
+  .mx-md-0 {
+    margin-left: 0 !important;
+  }
+  .m-md-1 {
+    margin: 0.25rem !important;
+  }
+  .mt-md-1,
+  .my-md-1 {
+    margin-top: 0.25rem !important;
+  }
+  .mr-md-1,
+  .mx-md-1 {
+    margin-right: 0.25rem !important;
+  }
+  .mb-md-1,
+  .my-md-1 {
+    margin-bottom: 0.25rem !important;
+  }
+  .ml-md-1,
+  .mx-md-1 {
+    margin-left: 0.25rem !important;
+  }
+  .m-md-2 {
+    margin: 0.5rem !important;
+  }
+  .mt-md-2,
+  .my-md-2 {
+    margin-top: 0.5rem !important;
+  }
+  .mr-md-2,
+  .mx-md-2 {
+    margin-right: 0.5rem !important;
+  }
+  .mb-md-2,
+  .my-md-2 {
+    margin-bottom: 0.5rem !important;
+  }
+  .ml-md-2,
+  .mx-md-2 {
+    margin-left: 0.5rem !important;
+  }
+  .m-md-3 {
+    margin: 1rem !important;
+  }
+  .mt-md-3,
+  .my-md-3 {
+    margin-top: 1rem !important;
+  }
+  .mr-md-3,
+  .mx-md-3 {
+    margin-right: 1rem !important;
+  }
+  .mb-md-3,
+  .my-md-3 {
+    margin-bottom: 1rem !important;
+  }
+  .ml-md-3,
+  .mx-md-3 {
+    margin-left: 1rem !important;
+  }
+  .m-md-4 {
+    margin: 1.5rem !important;
+  }
+  .mt-md-4,
+  .my-md-4 {
+    margin-top: 1.5rem !important;
+  }
+  .mr-md-4,
+  .mx-md-4 {
+    margin-right: 1.5rem !important;
+  }
+  .mb-md-4,
+  .my-md-4 {
+    margin-bottom: 1.5rem !important;
+  }
+  .ml-md-4,
+  .mx-md-4 {
+    margin-left: 1.5rem !important;
+  }
+  .m-md-5 {
+    margin: 3rem !important;
+  }
+  .mt-md-5,
+  .my-md-5 {
+    margin-top: 3rem !important;
+  }
+  .mr-md-5,
+  .mx-md-5 {
+    margin-right: 3rem !important;
+  }
+  .mb-md-5,
+  .my-md-5 {
+    margin-bottom: 3rem !important;
+  }
+  .ml-md-5,
+  .mx-md-5 {
+    margin-left: 3rem !important;
+  }
+  .p-md-0 {
+    padding: 0 !important;
+  }
+  .pt-md-0,
+  .py-md-0 {
+    padding-top: 0 !important;
+  }
+  .pr-md-0,
+  .px-md-0 {
+    padding-right: 0 !important;
+  }
+  .pb-md-0,
+  .py-md-0 {
+    padding-bottom: 0 !important;
+  }
+  .pl-md-0,
+  .px-md-0 {
+    padding-left: 0 !important;
+  }
+  .p-md-1 {
+    padding: 0.25rem !important;
+  }
+  .pt-md-1,
+  .py-md-1 {
+    padding-top: 0.25rem !important;
+  }
+  .pr-md-1,
+  .px-md-1 {
+    padding-right: 0.25rem !important;
+  }
+  .pb-md-1,
+  .py-md-1 {
+    padding-bottom: 0.25rem !important;
+  }
+  .pl-md-1,
+  .px-md-1 {
+    padding-left: 0.25rem !important;
+  }
+  .p-md-2 {
+    padding: 0.5rem !important;
+  }
+  .pt-md-2,
+  .py-md-2 {
+    padding-top: 0.5rem !important;
+  }
+  .pr-md-2,
+  .px-md-2 {
+    padding-right: 0.5rem !important;
+  }
+  .pb-md-2,
+  .py-md-2 {
+    padding-bottom: 0.5rem !important;
+  }
+  .pl-md-2,
+  .px-md-2 {
+    padding-left: 0.5rem !important;
+  }
+  .p-md-3 {
+    padding: 1rem !important;
+  }
+  .pt-md-3,
+  .py-md-3 {
+    padding-top: 1rem !important;
+  }
+  .pr-md-3,
+  .px-md-3 {
+    padding-right: 1rem !important;
+  }
+  .pb-md-3,
+  .py-md-3 {
+    padding-bottom: 1rem !important;
+  }
+  .pl-md-3,
+  .px-md-3 {
+    padding-left: 1rem !important;
+  }
+  .p-md-4 {
+    padding: 1.5rem !important;
+  }
+  .pt-md-4,
+  .py-md-4 {
+    padding-top: 1.5rem !important;
+  }
+  .pr-md-4,
+  .px-md-4 {
+    padding-right: 1.5rem !important;
+  }
+  .pb-md-4,
+  .py-md-4 {
+    padding-bottom: 1.5rem !important;
+  }
+  .pl-md-4,
+  .px-md-4 {
+    padding-left: 1.5rem !important;
+  }
+  .p-md-5 {
+    padding: 3rem !important;
+  }
+  .pt-md-5,
+  .py-md-5 {
+    padding-top: 3rem !important;
+  }
+  .pr-md-5,
+  .px-md-5 {
+    padding-right: 3rem !important;
+  }
+  .pb-md-5,
+  .py-md-5 {
+    padding-bottom: 3rem !important;
+  }
+  .pl-md-5,
+  .px-md-5 {
+    padding-left: 3rem !important;
+  }
+  .m-md-n1 {
+    margin: -0.25rem !important;
+  }
+  .mt-md-n1,
+  .my-md-n1 {
+    margin-top: -0.25rem !important;
+  }
+  .mr-md-n1,
+  .mx-md-n1 {
+    margin-right: -0.25rem !important;
+  }
+  .mb-md-n1,
+  .my-md-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+  .ml-md-n1,
+  .mx-md-n1 {
+    margin-left: -0.25rem !important;
+  }
+  .m-md-n2 {
+    margin: -0.5rem !important;
+  }
+  .mt-md-n2,
+  .my-md-n2 {
+    margin-top: -0.5rem !important;
+  }
+  .mr-md-n2,
+  .mx-md-n2 {
+    margin-right: -0.5rem !important;
+  }
+  .mb-md-n2,
+  .my-md-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+  .ml-md-n2,
+  .mx-md-n2 {
+    margin-left: -0.5rem !important;
+  }
+  .m-md-n3 {
+    margin: -1rem !important;
+  }
+  .mt-md-n3,
+  .my-md-n3 {
+    margin-top: -1rem !important;
+  }
+  .mr-md-n3,
+  .mx-md-n3 {
+    margin-right: -1rem !important;
+  }
+  .mb-md-n3,
+  .my-md-n3 {
+    margin-bottom: -1rem !important;
+  }
+  .ml-md-n3,
+  .mx-md-n3 {
+    margin-left: -1rem !important;
+  }
+  .m-md-n4 {
+    margin: -1.5rem !important;
+  }
+  .mt-md-n4,
+  .my-md-n4 {
+    margin-top: -1.5rem !important;
+  }
+  .mr-md-n4,
+  .mx-md-n4 {
+    margin-right: -1.5rem !important;
+  }
+  .mb-md-n4,
+  .my-md-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+  .ml-md-n4,
+  .mx-md-n4 {
+    margin-left: -1.5rem !important;
+  }
+  .m-md-n5 {
+    margin: -3rem !important;
+  }
+  .mt-md-n5,
+  .my-md-n5 {
+    margin-top: -3rem !important;
+  }
+  .mr-md-n5,
+  .mx-md-n5 {
+    margin-right: -3rem !important;
+  }
+  .mb-md-n5,
+  .my-md-n5 {
+    margin-bottom: -3rem !important;
+  }
+  .ml-md-n5,
+  .mx-md-n5 {
+    margin-left: -3rem !important;
+  }
+  .m-md-auto {
+    margin: auto !important;
+  }
+  .mt-md-auto,
+  .my-md-auto {
+    margin-top: auto !important;
+  }
+  .mr-md-auto,
+  .mx-md-auto {
+    margin-right: auto !important;
+  }
+  .mb-md-auto,
+  .my-md-auto {
+    margin-bottom: auto !important;
+  }
+  .ml-md-auto,
+  .mx-md-auto {
+    margin-left: auto !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .m-lg-0 {
+    margin: 0 !important;
+  }
+  .mt-lg-0,
+  .my-lg-0 {
+    margin-top: 0 !important;
+  }
+  .mr-lg-0,
+  .mx-lg-0 {
+    margin-right: 0 !important;
+  }
+  .mb-lg-0,
+  .my-lg-0 {
+    margin-bottom: 0 !important;
+  }
+  .ml-lg-0,
+  .mx-lg-0 {
+    margin-left: 0 !important;
+  }
+  .m-lg-1 {
+    margin: 0.25rem !important;
+  }
+  .mt-lg-1,
+  .my-lg-1 {
+    margin-top: 0.25rem !important;
+  }
+  .mr-lg-1,
+  .mx-lg-1 {
+    margin-right: 0.25rem !important;
+  }
+  .mb-lg-1,
+  .my-lg-1 {
+    margin-bottom: 0.25rem !important;
+  }
+  .ml-lg-1,
+  .mx-lg-1 {
+    margin-left: 0.25rem !important;
+  }
+  .m-lg-2 {
+    margin: 0.5rem !important;
+  }
+  .mt-lg-2,
+  .my-lg-2 {
+    margin-top: 0.5rem !important;
+  }
+  .mr-lg-2,
+  .mx-lg-2 {
+    margin-right: 0.5rem !important;
+  }
+  .mb-lg-2,
+  .my-lg-2 {
+    margin-bottom: 0.5rem !important;
+  }
+  .ml-lg-2,
+  .mx-lg-2 {
+    margin-left: 0.5rem !important;
+  }
+  .m-lg-3 {
+    margin: 1rem !important;
+  }
+  .mt-lg-3,
+  .my-lg-3 {
+    margin-top: 1rem !important;
+  }
+  .mr-lg-3,
+  .mx-lg-3 {
+    margin-right: 1rem !important;
+  }
+  .mb-lg-3,
+  .my-lg-3 {
+    margin-bottom: 1rem !important;
+  }
+  .ml-lg-3,
+  .mx-lg-3 {
+    margin-left: 1rem !important;
+  }
+  .m-lg-4 {
+    margin: 1.5rem !important;
+  }
+  .mt-lg-4,
+  .my-lg-4 {
+    margin-top: 1.5rem !important;
+  }
+  .mr-lg-4,
+  .mx-lg-4 {
+    margin-right: 1.5rem !important;
+  }
+  .mb-lg-4,
+  .my-lg-4 {
+    margin-bottom: 1.5rem !important;
+  }
+  .ml-lg-4,
+  .mx-lg-4 {
+    margin-left: 1.5rem !important;
+  }
+  .m-lg-5 {
+    margin: 3rem !important;
+  }
+  .mt-lg-5,
+  .my-lg-5 {
+    margin-top: 3rem !important;
+  }
+  .mr-lg-5,
+  .mx-lg-5 {
+    margin-right: 3rem !important;
+  }
+  .mb-lg-5,
+  .my-lg-5 {
+    margin-bottom: 3rem !important;
+  }
+  .ml-lg-5,
+  .mx-lg-5 {
+    margin-left: 3rem !important;
+  }
+  .p-lg-0 {
+    padding: 0 !important;
+  }
+  .pt-lg-0,
+  .py-lg-0 {
+    padding-top: 0 !important;
+  }
+  .pr-lg-0,
+  .px-lg-0 {
+    padding-right: 0 !important;
+  }
+  .pb-lg-0,
+  .py-lg-0 {
+    padding-bottom: 0 !important;
+  }
+  .pl-lg-0,
+  .px-lg-0 {
+    padding-left: 0 !important;
+  }
+  .p-lg-1 {
+    padding: 0.25rem !important;
+  }
+  .pt-lg-1,
+  .py-lg-1 {
+    padding-top: 0.25rem !important;
+  }
+  .pr-lg-1,
+  .px-lg-1 {
+    padding-right: 0.25rem !important;
+  }
+  .pb-lg-1,
+  .py-lg-1 {
+    padding-bottom: 0.25rem !important;
+  }
+  .pl-lg-1,
+  .px-lg-1 {
+    padding-left: 0.25rem !important;
+  }
+  .p-lg-2 {
+    padding: 0.5rem !important;
+  }
+  .pt-lg-2,
+  .py-lg-2 {
+    padding-top: 0.5rem !important;
+  }
+  .pr-lg-2,
+  .px-lg-2 {
+    padding-right: 0.5rem !important;
+  }
+  .pb-lg-2,
+  .py-lg-2 {
+    padding-bottom: 0.5rem !important;
+  }
+  .pl-lg-2,
+  .px-lg-2 {
+    padding-left: 0.5rem !important;
+  }
+  .p-lg-3 {
+    padding: 1rem !important;
+  }
+  .pt-lg-3,
+  .py-lg-3 {
+    padding-top: 1rem !important;
+  }
+  .pr-lg-3,
+  .px-lg-3 {
+    padding-right: 1rem !important;
+  }
+  .pb-lg-3,
+  .py-lg-3 {
+    padding-bottom: 1rem !important;
+  }
+  .pl-lg-3,
+  .px-lg-3 {
+    padding-left: 1rem !important;
+  }
+  .p-lg-4 {
+    padding: 1.5rem !important;
+  }
+  .pt-lg-4,
+  .py-lg-4 {
+    padding-top: 1.5rem !important;
+  }
+  .pr-lg-4,
+  .px-lg-4 {
+    padding-right: 1.5rem !important;
+  }
+  .pb-lg-4,
+  .py-lg-4 {
+    padding-bottom: 1.5rem !important;
+  }
+  .pl-lg-4,
+  .px-lg-4 {
+    padding-left: 1.5rem !important;
+  }
+  .p-lg-5 {
+    padding: 3rem !important;
+  }
+  .pt-lg-5,
+  .py-lg-5 {
+    padding-top: 3rem !important;
+  }
+  .pr-lg-5,
+  .px-lg-5 {
+    padding-right: 3rem !important;
+  }
+  .pb-lg-5,
+  .py-lg-5 {
+    padding-bottom: 3rem !important;
+  }
+  .pl-lg-5,
+  .px-lg-5 {
+    padding-left: 3rem !important;
+  }
+  .m-lg-n1 {
+    margin: -0.25rem !important;
+  }
+  .mt-lg-n1,
+  .my-lg-n1 {
+    margin-top: -0.25rem !important;
+  }
+  .mr-lg-n1,
+  .mx-lg-n1 {
+    margin-right: -0.25rem !important;
+  }
+  .mb-lg-n1,
+  .my-lg-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+  .ml-lg-n1,
+  .mx-lg-n1 {
+    margin-left: -0.25rem !important;
+  }
+  .m-lg-n2 {
+    margin: -0.5rem !important;
+  }
+  .mt-lg-n2,
+  .my-lg-n2 {
+    margin-top: -0.5rem !important;
+  }
+  .mr-lg-n2,
+  .mx-lg-n2 {
+    margin-right: -0.5rem !important;
+  }
+  .mb-lg-n2,
+  .my-lg-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+  .ml-lg-n2,
+  .mx-lg-n2 {
+    margin-left: -0.5rem !important;
+  }
+  .m-lg-n3 {
+    margin: -1rem !important;
+  }
+  .mt-lg-n3,
+  .my-lg-n3 {
+    margin-top: -1rem !important;
+  }
+  .mr-lg-n3,
+  .mx-lg-n3 {
+    margin-right: -1rem !important;
+  }
+  .mb-lg-n3,
+  .my-lg-n3 {
+    margin-bottom: -1rem !important;
+  }
+  .ml-lg-n3,
+  .mx-lg-n3 {
+    margin-left: -1rem !important;
+  }
+  .m-lg-n4 {
+    margin: -1.5rem !important;
+  }
+  .mt-lg-n4,
+  .my-lg-n4 {
+    margin-top: -1.5rem !important;
+  }
+  .mr-lg-n4,
+  .mx-lg-n4 {
+    margin-right: -1.5rem !important;
+  }
+  .mb-lg-n4,
+  .my-lg-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+  .ml-lg-n4,
+  .mx-lg-n4 {
+    margin-left: -1.5rem !important;
+  }
+  .m-lg-n5 {
+    margin: -3rem !important;
+  }
+  .mt-lg-n5,
+  .my-lg-n5 {
+    margin-top: -3rem !important;
+  }
+  .mr-lg-n5,
+  .mx-lg-n5 {
+    margin-right: -3rem !important;
+  }
+  .mb-lg-n5,
+  .my-lg-n5 {
+    margin-bottom: -3rem !important;
+  }
+  .ml-lg-n5,
+  .mx-lg-n5 {
+    margin-left: -3rem !important;
+  }
+  .m-lg-auto {
+    margin: auto !important;
+  }
+  .mt-lg-auto,
+  .my-lg-auto {
+    margin-top: auto !important;
+  }
+  .mr-lg-auto,
+  .mx-lg-auto {
+    margin-right: auto !important;
+  }
+  .mb-lg-auto,
+  .my-lg-auto {
+    margin-bottom: auto !important;
+  }
+  .ml-lg-auto,
+  .mx-lg-auto {
+    margin-left: auto !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .m-xl-0 {
+    margin: 0 !important;
+  }
+  .mt-xl-0,
+  .my-xl-0 {
+    margin-top: 0 !important;
+  }
+  .mr-xl-0,
+  .mx-xl-0 {
+    margin-right: 0 !important;
+  }
+  .mb-xl-0,
+  .my-xl-0 {
+    margin-bottom: 0 !important;
+  }
+  .ml-xl-0,
+  .mx-xl-0 {
+    margin-left: 0 !important;
+  }
+  .m-xl-1 {
+    margin: 0.25rem !important;
+  }
+  .mt-xl-1,
+  .my-xl-1 {
+    margin-top: 0.25rem !important;
+  }
+  .mr-xl-1,
+  .mx-xl-1 {
+    margin-right: 0.25rem !important;
+  }
+  .mb-xl-1,
+  .my-xl-1 {
+    margin-bottom: 0.25rem !important;
+  }
+  .ml-xl-1,
+  .mx-xl-1 {
+    margin-left: 0.25rem !important;
+  }
+  .m-xl-2 {
+    margin: 0.5rem !important;
+  }
+  .mt-xl-2,
+  .my-xl-2 {
+    margin-top: 0.5rem !important;
+  }
+  .mr-xl-2,
+  .mx-xl-2 {
+    margin-right: 0.5rem !important;
+  }
+  .mb-xl-2,
+  .my-xl-2 {
+    margin-bottom: 0.5rem !important;
+  }
+  .ml-xl-2,
+  .mx-xl-2 {
+    margin-left: 0.5rem !important;
+  }
+  .m-xl-3 {
+    margin: 1rem !important;
+  }
+  .mt-xl-3,
+  .my-xl-3 {
+    margin-top: 1rem !important;
+  }
+  .mr-xl-3,
+  .mx-xl-3 {
+    margin-right: 1rem !important;
+  }
+  .mb-xl-3,
+  .my-xl-3 {
+    margin-bottom: 1rem !important;
+  }
+  .ml-xl-3,
+  .mx-xl-3 {
+    margin-left: 1rem !important;
+  }
+  .m-xl-4 {
+    margin: 1.5rem !important;
+  }
+  .mt-xl-4,
+  .my-xl-4 {
+    margin-top: 1.5rem !important;
+  }
+  .mr-xl-4,
+  .mx-xl-4 {
+    margin-right: 1.5rem !important;
+  }
+  .mb-xl-4,
+  .my-xl-4 {
+    margin-bottom: 1.5rem !important;
+  }
+  .ml-xl-4,
+  .mx-xl-4 {
+    margin-left: 1.5rem !important;
+  }
+  .m-xl-5 {
+    margin: 3rem !important;
+  }
+  .mt-xl-5,
+  .my-xl-5 {
+    margin-top: 3rem !important;
+  }
+  .mr-xl-5,
+  .mx-xl-5 {
+    margin-right: 3rem !important;
+  }
+  .mb-xl-5,
+  .my-xl-5 {
+    margin-bottom: 3rem !important;
+  }
+  .ml-xl-5,
+  .mx-xl-5 {
+    margin-left: 3rem !important;
+  }
+  .p-xl-0 {
+    padding: 0 !important;
+  }
+  .pt-xl-0,
+  .py-xl-0 {
+    padding-top: 0 !important;
+  }
+  .pr-xl-0,
+  .px-xl-0 {
+    padding-right: 0 !important;
+  }
+  .pb-xl-0,
+  .py-xl-0 {
+    padding-bottom: 0 !important;
+  }
+  .pl-xl-0,
+  .px-xl-0 {
+    padding-left: 0 !important;
+  }
+  .p-xl-1 {
+    padding: 0.25rem !important;
+  }
+  .pt-xl-1,
+  .py-xl-1 {
+    padding-top: 0.25rem !important;
+  }
+  .pr-xl-1,
+  .px-xl-1 {
+    padding-right: 0.25rem !important;
+  }
+  .pb-xl-1,
+  .py-xl-1 {
+    padding-bottom: 0.25rem !important;
+  }
+  .pl-xl-1,
+  .px-xl-1 {
+    padding-left: 0.25rem !important;
+  }
+  .p-xl-2 {
+    padding: 0.5rem !important;
+  }
+  .pt-xl-2,
+  .py-xl-2 {
+    padding-top: 0.5rem !important;
+  }
+  .pr-xl-2,
+  .px-xl-2 {
+    padding-right: 0.5rem !important;
+  }
+  .pb-xl-2,
+  .py-xl-2 {
+    padding-bottom: 0.5rem !important;
+  }
+  .pl-xl-2,
+  .px-xl-2 {
+    padding-left: 0.5rem !important;
+  }
+  .p-xl-3 {
+    padding: 1rem !important;
+  }
+  .pt-xl-3,
+  .py-xl-3 {
+    padding-top: 1rem !important;
+  }
+  .pr-xl-3,
+  .px-xl-3 {
+    padding-right: 1rem !important;
+  }
+  .pb-xl-3,
+  .py-xl-3 {
+    padding-bottom: 1rem !important;
+  }
+  .pl-xl-3,
+  .px-xl-3 {
+    padding-left: 1rem !important;
+  }
+  .p-xl-4 {
+    padding: 1.5rem !important;
+  }
+  .pt-xl-4,
+  .py-xl-4 {
+    padding-top: 1.5rem !important;
+  }
+  .pr-xl-4,
+  .px-xl-4 {
+    padding-right: 1.5rem !important;
+  }
+  .pb-xl-4,
+  .py-xl-4 {
+    padding-bottom: 1.5rem !important;
+  }
+  .pl-xl-4,
+  .px-xl-4 {
+    padding-left: 1.5rem !important;
+  }
+  .p-xl-5 {
+    padding: 3rem !important;
+  }
+  .pt-xl-5,
+  .py-xl-5 {
+    padding-top: 3rem !important;
+  }
+  .pr-xl-5,
+  .px-xl-5 {
+    padding-right: 3rem !important;
+  }
+  .pb-xl-5,
+  .py-xl-5 {
+    padding-bottom: 3rem !important;
+  }
+  .pl-xl-5,
+  .px-xl-5 {
+    padding-left: 3rem !important;
+  }
+  .m-xl-n1 {
+    margin: -0.25rem !important;
+  }
+  .mt-xl-n1,
+  .my-xl-n1 {
+    margin-top: -0.25rem !important;
+  }
+  .mr-xl-n1,
+  .mx-xl-n1 {
+    margin-right: -0.25rem !important;
+  }
+  .mb-xl-n1,
+  .my-xl-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+  .ml-xl-n1,
+  .mx-xl-n1 {
+    margin-left: -0.25rem !important;
+  }
+  .m-xl-n2 {
+    margin: -0.5rem !important;
+  }
+  .mt-xl-n2,
+  .my-xl-n2 {
+    margin-top: -0.5rem !important;
+  }
+  .mr-xl-n2,
+  .mx-xl-n2 {
+    margin-right: -0.5rem !important;
+  }
+  .mb-xl-n2,
+  .my-xl-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+  .ml-xl-n2,
+  .mx-xl-n2 {
+    margin-left: -0.5rem !important;
+  }
+  .m-xl-n3 {
+    margin: -1rem !important;
+  }
+  .mt-xl-n3,
+  .my-xl-n3 {
+    margin-top: -1rem !important;
+  }
+  .mr-xl-n3,
+  .mx-xl-n3 {
+    margin-right: -1rem !important;
+  }
+  .mb-xl-n3,
+  .my-xl-n3 {
+    margin-bottom: -1rem !important;
+  }
+  .ml-xl-n3,
+  .mx-xl-n3 {
+    margin-left: -1rem !important;
+  }
+  .m-xl-n4 {
+    margin: -1.5rem !important;
+  }
+  .mt-xl-n4,
+  .my-xl-n4 {
+    margin-top: -1.5rem !important;
+  }
+  .mr-xl-n4,
+  .mx-xl-n4 {
+    margin-right: -1.5rem !important;
+  }
+  .mb-xl-n4,
+  .my-xl-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+  .ml-xl-n4,
+  .mx-xl-n4 {
+    margin-left: -1.5rem !important;
+  }
+  .m-xl-n5 {
+    margin: -3rem !important;
+  }
+  .mt-xl-n5,
+  .my-xl-n5 {
+    margin-top: -3rem !important;
+  }
+  .mr-xl-n5,
+  .mx-xl-n5 {
+    margin-right: -3rem !important;
+  }
+  .mb-xl-n5,
+  .my-xl-n5 {
+    margin-bottom: -3rem !important;
+  }
+  .ml-xl-n5,
+  .mx-xl-n5 {
+    margin-left: -3rem !important;
+  }
+  .m-xl-auto {
+    margin: auto !important;
+  }
+  .mt-xl-auto,
+  .my-xl-auto {
+    margin-top: auto !important;
+  }
+  .mr-xl-auto,
+  .mx-xl-auto {
+    margin-right: auto !important;
+  }
+  .mb-xl-auto,
+  .my-xl-auto {
+    margin-bottom: auto !important;
+  }
+  .ml-xl-auto,
+  .mx-xl-auto {
+    margin-left: auto !important;
+  }
+}
+/*# sourceMappingURL=bootstrap-grid.css.map */

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 0
vendor/bootstrap/scss/bootstrap-grid.css.map


+ 327 - 0
vendor/bootstrap/scss/bootstrap-reboot.css

@@ -0,0 +1,327 @@
+/*!
+ * Bootstrap Reboot v4.6.0 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors
+ * Copyright 2011-2021 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
+ */
+*,
+*::before,
+*::after {
+  -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+html {
+  font-family: sans-serif;
+  line-height: 1.15;
+  -webkit-text-size-adjust: 100%;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
+  display: block;
+}
+
+body {
+  margin: 0;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-size: 1rem;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #212529;
+  text-align: left;
+  background-color: #fff;
+}
+
+[tabindex="-1"]:focus:not(:focus-visible) {
+  outline: 0 !important;
+}
+
+hr {
+  -webkit-box-sizing: content-box;
+          box-sizing: content-box;
+  height: 0;
+  overflow: visible;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  margin-top: 0;
+  margin-bottom: 0.5rem;
+}
+
+p {
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+abbr[title],
+abbr[data-original-title] {
+  text-decoration: underline;
+  -webkit-text-decoration: underline dotted;
+          text-decoration: underline dotted;
+  cursor: help;
+  border-bottom: 0;
+  text-decoration-skip-ink: none;
+}
+
+address {
+  margin-bottom: 1rem;
+  font-style: normal;
+  line-height: inherit;
+}
+
+ol,
+ul,
+dl {
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+ol ol,
+ul ul,
+ol ul,
+ul ol {
+  margin-bottom: 0;
+}
+
+dt {
+  font-weight: 700;
+}
+
+dd {
+  margin-bottom: .5rem;
+  margin-left: 0;
+}
+
+blockquote {
+  margin: 0 0 1rem;
+}
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+small {
+  font-size: 80%;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -.25em;
+}
+
+sup {
+  top: -.5em;
+}
+
+a {
+  color: #007bff;
+  text-decoration: none;
+  background-color: transparent;
+}
+
+a:hover {
+  color: #0056b3;
+  text-decoration: underline;
+}
+
+a:not([href]):not([class]) {
+  color: inherit;
+  text-decoration: none;
+}
+
+a:not([href]):not([class]):hover {
+  color: inherit;
+  text-decoration: none;
+}
+
+pre,
+code,
+kbd,
+samp {
+  font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  font-size: 1em;
+}
+
+pre {
+  margin-top: 0;
+  margin-bottom: 1rem;
+  overflow: auto;
+  -ms-overflow-style: scrollbar;
+}
+
+figure {
+  margin: 0 0 1rem;
+}
+
+img {
+  vertical-align: middle;
+  border-style: none;
+}
+
+svg {
+  overflow: hidden;
+  vertical-align: middle;
+}
+
+table {
+  border-collapse: collapse;
+}
+
+caption {
+  padding-top: 0.75rem;
+  padding-bottom: 0.75rem;
+  color: #6c757d;
+  text-align: left;
+  caption-side: bottom;
+}
+
+th {
+  text-align: inherit;
+  text-align: -webkit-match-parent;
+}
+
+label {
+  display: inline-block;
+  margin-bottom: 0.5rem;
+}
+
+button {
+  border-radius: 0;
+}
+
+button:focus:not(:focus-visible) {
+  outline: 0;
+}
+
+input,
+button,
+select,
+optgroup,
+textarea {
+  margin: 0;
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+button,
+input {
+  overflow: visible;
+}
+
+button,
+select {
+  text-transform: none;
+}
+
+[role="button"] {
+  cursor: pointer;
+}
+
+select {
+  word-wrap: normal;
+}
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+  -webkit-appearance: button;
+}
+
+button:not(:disabled),
+[type="button"]:not(:disabled),
+[type="reset"]:not(:disabled),
+[type="submit"]:not(:disabled) {
+  cursor: pointer;
+}
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+  padding: 0;
+  border-style: none;
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+  -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+  padding: 0;
+}
+
+textarea {
+  overflow: auto;
+  resize: vertical;
+}
+
+fieldset {
+  min-width: 0;
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  max-width: 100%;
+  padding: 0;
+  margin-bottom: .5rem;
+  font-size: 1.5rem;
+  line-height: inherit;
+  color: inherit;
+  white-space: normal;
+}
+
+progress {
+  vertical-align: baseline;
+}
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+[type="search"] {
+  outline-offset: -2px;
+  -webkit-appearance: none;
+}
+
+[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+::-webkit-file-upload-button {
+  font: inherit;
+  -webkit-appearance: button;
+}
+
+output {
+  display: inline-block;
+}
+
+summary {
+  display: list-item;
+  cursor: pointer;
+}
+
+template {
+  display: none;
+}
+
+[hidden] {
+  display: none !important;
+}
+/*# sourceMappingURL=bootstrap-reboot.css.map */

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 0
vendor/bootstrap/scss/bootstrap-reboot.css.map


+ 10840 - 0
vendor/bootstrap/scss/bootstrap.css

@@ -0,0 +1,10840 @@
+/*!
+ * Bootstrap v4.6.0 (https://getbootstrap.com/)
+ * Copyright 2011-2021 The Bootstrap Authors
+ * Copyright 2011-2021 Twitter, Inc.
+ * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
+ */
+:root {
+  --blue: #007bff;
+  --indigo: #6610f2;
+  --purple: #6f42c1;
+  --pink: #e83e8c;
+  --red: #dc3545;
+  --orange: #fd7e14;
+  --yellow: #ffc107;
+  --green: #28a745;
+  --teal: #20c997;
+  --cyan: #17a2b8;
+  --white: #fff;
+  --gray: #6c757d;
+  --gray-dark: #343a40;
+  --primary: #007bff;
+  --secondary: #6c757d;
+  --success: #28a745;
+  --info: #17a2b8;
+  --warning: #ffc107;
+  --danger: #dc3545;
+  --light: #f8f9fa;
+  --dark: #343a40;
+  --breakpoint-xs: 0;
+  --breakpoint-sm: 576px;
+  --breakpoint-md: 768px;
+  --breakpoint-lg: 992px;
+  --breakpoint-xl: 1200px;
+  --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+}
+
+*,
+*::before,
+*::after {
+  -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+}
+
+html {
+  font-family: sans-serif;
+  line-height: 1.15;
+  -webkit-text-size-adjust: 100%;
+  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
+  display: block;
+}
+
+body {
+  margin: 0;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-size: 1rem;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #212529;
+  text-align: left;
+  background-color: #fff;
+}
+
+[tabindex="-1"]:focus:not(:focus-visible) {
+  outline: 0 !important;
+}
+
+hr {
+  -webkit-box-sizing: content-box;
+          box-sizing: content-box;
+  height: 0;
+  overflow: visible;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  margin-top: 0;
+  margin-bottom: 0.5rem;
+}
+
+p {
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+abbr[title],
+abbr[data-original-title] {
+  text-decoration: underline;
+  -webkit-text-decoration: underline dotted;
+          text-decoration: underline dotted;
+  cursor: help;
+  border-bottom: 0;
+  text-decoration-skip-ink: none;
+}
+
+address {
+  margin-bottom: 1rem;
+  font-style: normal;
+  line-height: inherit;
+}
+
+ol,
+ul,
+dl {
+  margin-top: 0;
+  margin-bottom: 1rem;
+}
+
+ol ol,
+ul ul,
+ol ul,
+ul ol {
+  margin-bottom: 0;
+}
+
+dt {
+  font-weight: 700;
+}
+
+dd {
+  margin-bottom: .5rem;
+  margin-left: 0;
+}
+
+blockquote {
+  margin: 0 0 1rem;
+}
+
+b,
+strong {
+  font-weight: bolder;
+}
+
+small {
+  font-size: 80%;
+}
+
+sub,
+sup {
+  position: relative;
+  font-size: 75%;
+  line-height: 0;
+  vertical-align: baseline;
+}
+
+sub {
+  bottom: -.25em;
+}
+
+sup {
+  top: -.5em;
+}
+
+a {
+  color: #007bff;
+  text-decoration: none;
+  background-color: transparent;
+}
+
+a:hover {
+  color: #0056b3;
+  text-decoration: underline;
+}
+
+a:not([href]):not([class]) {
+  color: inherit;
+  text-decoration: none;
+}
+
+a:not([href]):not([class]):hover {
+  color: inherit;
+  text-decoration: none;
+}
+
+pre,
+code,
+kbd,
+samp {
+  font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
+  font-size: 1em;
+}
+
+pre {
+  margin-top: 0;
+  margin-bottom: 1rem;
+  overflow: auto;
+  -ms-overflow-style: scrollbar;
+}
+
+figure {
+  margin: 0 0 1rem;
+}
+
+img {
+  vertical-align: middle;
+  border-style: none;
+}
+
+svg {
+  overflow: hidden;
+  vertical-align: middle;
+}
+
+table {
+  border-collapse: collapse;
+}
+
+caption {
+  padding-top: 0.75rem;
+  padding-bottom: 0.75rem;
+  color: #6c757d;
+  text-align: left;
+  caption-side: bottom;
+}
+
+th {
+  text-align: inherit;
+  text-align: -webkit-match-parent;
+}
+
+label {
+  display: inline-block;
+  margin-bottom: 0.5rem;
+}
+
+button {
+  border-radius: 0;
+}
+
+button:focus:not(:focus-visible) {
+  outline: 0;
+}
+
+input,
+button,
+select,
+optgroup,
+textarea {
+  margin: 0;
+  font-family: inherit;
+  font-size: inherit;
+  line-height: inherit;
+}
+
+button,
+input {
+  overflow: visible;
+}
+
+button,
+select {
+  text-transform: none;
+}
+
+[role="button"] {
+  cursor: pointer;
+}
+
+select {
+  word-wrap: normal;
+}
+
+button,
+[type="button"],
+[type="reset"],
+[type="submit"] {
+  -webkit-appearance: button;
+}
+
+button:not(:disabled),
+[type="button"]:not(:disabled),
+[type="reset"]:not(:disabled),
+[type="submit"]:not(:disabled) {
+  cursor: pointer;
+}
+
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
+[type="reset"]::-moz-focus-inner,
+[type="submit"]::-moz-focus-inner {
+  padding: 0;
+  border-style: none;
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+  -webkit-box-sizing: border-box;
+          box-sizing: border-box;
+  padding: 0;
+}
+
+textarea {
+  overflow: auto;
+  resize: vertical;
+}
+
+fieldset {
+  min-width: 0;
+  padding: 0;
+  margin: 0;
+  border: 0;
+}
+
+legend {
+  display: block;
+  width: 100%;
+  max-width: 100%;
+  padding: 0;
+  margin-bottom: .5rem;
+  font-size: 1.5rem;
+  line-height: inherit;
+  color: inherit;
+  white-space: normal;
+}
+
+progress {
+  vertical-align: baseline;
+}
+
+[type="number"]::-webkit-inner-spin-button,
+[type="number"]::-webkit-outer-spin-button {
+  height: auto;
+}
+
+[type="search"] {
+  outline-offset: -2px;
+  -webkit-appearance: none;
+}
+
+[type="search"]::-webkit-search-decoration {
+  -webkit-appearance: none;
+}
+
+::-webkit-file-upload-button {
+  font: inherit;
+  -webkit-appearance: button;
+}
+
+output {
+  display: inline-block;
+}
+
+summary {
+  display: list-item;
+  cursor: pointer;
+}
+
+template {
+  display: none;
+}
+
+[hidden] {
+  display: none !important;
+}
+
+h1, h2, h3, h4, h5, h6,
+.h1, .h2, .h3, .h4, .h5, .h6 {
+  margin-bottom: 0.5rem;
+  font-weight: 500;
+  line-height: 1.2;
+}
+
+h1, .h1 {
+  font-size: 2.5rem;
+}
+
+h2, .h2 {
+  font-size: 2rem;
+}
+
+h3, .h3 {
+  font-size: 1.75rem;
+}
+
+h4, .h4 {
+  font-size: 1.5rem;
+}
+
+h5, .h5 {
+  font-size: 1.25rem;
+}
+
+h6, .h6 {
+  font-size: 1rem;
+}
+
+.lead {
+  font-size: 1.25rem;
+  font-weight: 300;
+}
+
+.display-1 {
+  font-size: 6rem;
+  font-weight: 300;
+  line-height: 1.2;
+}
+
+.display-2 {
+  font-size: 5.5rem;
+  font-weight: 300;
+  line-height: 1.2;
+}
+
+.display-3 {
+  font-size: 4.5rem;
+  font-weight: 300;
+  line-height: 1.2;
+}
+
+.display-4 {
+  font-size: 3.5rem;
+  font-weight: 300;
+  line-height: 1.2;
+}
+
+hr {
+  margin-top: 1rem;
+  margin-bottom: 1rem;
+  border: 0;
+  border-top: 1px solid rgba(0, 0, 0, 0.1);
+}
+
+small,
+.small {
+  font-size: 80%;
+  font-weight: 400;
+}
+
+mark,
+.mark {
+  padding: 0.2em;
+  background-color: #fcf8e3;
+}
+
+.list-unstyled {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline {
+  padding-left: 0;
+  list-style: none;
+}
+
+.list-inline-item {
+  display: inline-block;
+}
+
+.list-inline-item:not(:last-child) {
+  margin-right: 0.5rem;
+}
+
+.initialism {
+  font-size: 90%;
+  text-transform: uppercase;
+}
+
+.blockquote {
+  margin-bottom: 1rem;
+  font-size: 1.25rem;
+}
+
+.blockquote-footer {
+  display: block;
+  font-size: 80%;
+  color: #6c757d;
+}
+
+.blockquote-footer::before {
+  content: "\2014\00A0";
+}
+
+.img-fluid {
+  max-width: 100%;
+  height: auto;
+}
+
+.img-thumbnail {
+  padding: 0.25rem;
+  background-color: #fff;
+  border: 1px solid #dee2e6;
+  border-radius: 0.25rem;
+  max-width: 100%;
+  height: auto;
+}
+
+.figure {
+  display: inline-block;
+}
+
+.figure-img {
+  margin-bottom: 0.5rem;
+  line-height: 1;
+}
+
+.figure-caption {
+  font-size: 90%;
+  color: #6c757d;
+}
+
+code {
+  font-size: 87.5%;
+  color: #e83e8c;
+  word-wrap: break-word;
+}
+
+a > code {
+  color: inherit;
+}
+
+kbd {
+  padding: 0.2rem 0.4rem;
+  font-size: 87.5%;
+  color: #fff;
+  background-color: #212529;
+  border-radius: 0.2rem;
+}
+
+kbd kbd {
+  padding: 0;
+  font-size: 100%;
+  font-weight: 700;
+}
+
+pre {
+  display: block;
+  font-size: 87.5%;
+  color: #212529;
+}
+
+pre code {
+  font-size: inherit;
+  color: inherit;
+  word-break: normal;
+}
+
+.pre-scrollable {
+  max-height: 340px;
+  overflow-y: scroll;
+}
+
+.container,
+.container-fluid,
+.container-sm,
+.container-md,
+.container-lg,
+.container-xl {
+  width: 100%;
+  padding-right: 15px;
+  padding-left: 15px;
+  margin-right: auto;
+  margin-left: auto;
+}
+
+@media (min-width: 576px) {
+  .container, .container-sm {
+    max-width: 540px;
+  }
+}
+
+@media (min-width: 768px) {
+  .container, .container-sm, .container-md {
+    max-width: 720px;
+  }
+}
+
+@media (min-width: 992px) {
+  .container, .container-sm, .container-md, .container-lg {
+    max-width: 960px;
+  }
+}
+
+@media (min-width: 1200px) {
+  .container, .container-sm, .container-md, .container-lg, .container-xl {
+    max-width: 1140px;
+  }
+}
+
+.row {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  margin-right: -15px;
+  margin-left: -15px;
+}
+
+.no-gutters {
+  margin-right: 0;
+  margin-left: 0;
+}
+
+.no-gutters > .col,
+.no-gutters > [class*="col-"] {
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,
+.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,
+.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,
+.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,
+.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,
+.col-xl-auto {
+  position: relative;
+  width: 100%;
+  padding-right: 15px;
+  padding-left: 15px;
+}
+
+.col {
+  -ms-flex-preferred-size: 0;
+      flex-basis: 0;
+  -webkit-box-flex: 1;
+      -ms-flex-positive: 1;
+          flex-grow: 1;
+  max-width: 100%;
+}
+
+.row-cols-1 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 100%;
+          flex: 0 0 100%;
+  max-width: 100%;
+}
+
+.row-cols-2 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 50%;
+          flex: 0 0 50%;
+  max-width: 50%;
+}
+
+.row-cols-3 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 33.33333%;
+          flex: 0 0 33.33333%;
+  max-width: 33.33333%;
+}
+
+.row-cols-4 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 25%;
+          flex: 0 0 25%;
+  max-width: 25%;
+}
+
+.row-cols-5 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 20%;
+          flex: 0 0 20%;
+  max-width: 20%;
+}
+
+.row-cols-6 > * {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 16.66667%;
+          flex: 0 0 16.66667%;
+  max-width: 16.66667%;
+}
+
+.col-auto {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 auto;
+          flex: 0 0 auto;
+  width: auto;
+  max-width: 100%;
+}
+
+.col-1 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 8.33333%;
+          flex: 0 0 8.33333%;
+  max-width: 8.33333%;
+}
+
+.col-2 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 16.66667%;
+          flex: 0 0 16.66667%;
+  max-width: 16.66667%;
+}
+
+.col-3 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 25%;
+          flex: 0 0 25%;
+  max-width: 25%;
+}
+
+.col-4 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 33.33333%;
+          flex: 0 0 33.33333%;
+  max-width: 33.33333%;
+}
+
+.col-5 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 41.66667%;
+          flex: 0 0 41.66667%;
+  max-width: 41.66667%;
+}
+
+.col-6 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 50%;
+          flex: 0 0 50%;
+  max-width: 50%;
+}
+
+.col-7 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 58.33333%;
+          flex: 0 0 58.33333%;
+  max-width: 58.33333%;
+}
+
+.col-8 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 66.66667%;
+          flex: 0 0 66.66667%;
+  max-width: 66.66667%;
+}
+
+.col-9 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 75%;
+          flex: 0 0 75%;
+  max-width: 75%;
+}
+
+.col-10 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 83.33333%;
+          flex: 0 0 83.33333%;
+  max-width: 83.33333%;
+}
+
+.col-11 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 91.66667%;
+          flex: 0 0 91.66667%;
+  max-width: 91.66667%;
+}
+
+.col-12 {
+  -webkit-box-flex: 0;
+      -ms-flex: 0 0 100%;
+          flex: 0 0 100%;
+  max-width: 100%;
+}
+
+.order-first {
+  -webkit-box-ordinal-group: 0;
+      -ms-flex-order: -1;
+          order: -1;
+}
+
+.order-last {
+  -webkit-box-ordinal-group: 14;
+      -ms-flex-order: 13;
+          order: 13;
+}
+
+.order-0 {
+  -webkit-box-ordinal-group: 1;
+      -ms-flex-order: 0;
+          order: 0;
+}
+
+.order-1 {
+  -webkit-box-ordinal-group: 2;
+      -ms-flex-order: 1;
+          order: 1;
+}
+
+.order-2 {
+  -webkit-box-ordinal-group: 3;
+      -ms-flex-order: 2;
+          order: 2;
+}
+
+.order-3 {
+  -webkit-box-ordinal-group: 4;
+      -ms-flex-order: 3;
+          order: 3;
+}
+
+.order-4 {
+  -webkit-box-ordinal-group: 5;
+      -ms-flex-order: 4;
+          order: 4;
+}
+
+.order-5 {
+  -webkit-box-ordinal-group: 6;
+      -ms-flex-order: 5;
+          order: 5;
+}
+
+.order-6 {
+  -webkit-box-ordinal-group: 7;
+      -ms-flex-order: 6;
+          order: 6;
+}
+
+.order-7 {
+  -webkit-box-ordinal-group: 8;
+      -ms-flex-order: 7;
+          order: 7;
+}
+
+.order-8 {
+  -webkit-box-ordinal-group: 9;
+      -ms-flex-order: 8;
+          order: 8;
+}
+
+.order-9 {
+  -webkit-box-ordinal-group: 10;
+      -ms-flex-order: 9;
+          order: 9;
+}
+
+.order-10 {
+  -webkit-box-ordinal-group: 11;
+      -ms-flex-order: 10;
+          order: 10;
+}
+
+.order-11 {
+  -webkit-box-ordinal-group: 12;
+      -ms-flex-order: 11;
+          order: 11;
+}
+
+.order-12 {
+  -webkit-box-ordinal-group: 13;
+      -ms-flex-order: 12;
+          order: 12;
+}
+
+.offset-1 {
+  margin-left: 8.33333%;
+}
+
+.offset-2 {
+  margin-left: 16.66667%;
+}
+
+.offset-3 {
+  margin-left: 25%;
+}
+
+.offset-4 {
+  margin-left: 33.33333%;
+}
+
+.offset-5 {
+  margin-left: 41.66667%;
+}
+
+.offset-6 {
+  margin-left: 50%;
+}
+
+.offset-7 {
+  margin-left: 58.33333%;
+}
+
+.offset-8 {
+  margin-left: 66.66667%;
+}
+
+.offset-9 {
+  margin-left: 75%;
+}
+
+.offset-10 {
+  margin-left: 83.33333%;
+}
+
+.offset-11 {
+  margin-left: 91.66667%;
+}
+
+@media (min-width: 576px) {
+  .col-sm {
+    -ms-flex-preferred-size: 0;
+        flex-basis: 0;
+    -webkit-box-flex: 1;
+        -ms-flex-positive: 1;
+            flex-grow: 1;
+    max-width: 100%;
+  }
+  .row-cols-sm-1 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .row-cols-sm-2 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .row-cols-sm-3 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .row-cols-sm-4 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .row-cols-sm-5 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 20%;
+            flex: 0 0 20%;
+    max-width: 20%;
+  }
+  .row-cols-sm-6 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-sm-auto {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 auto;
+            flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+  .col-sm-1 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 8.33333%;
+            flex: 0 0 8.33333%;
+    max-width: 8.33333%;
+  }
+  .col-sm-2 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-sm-3 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .col-sm-4 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .col-sm-5 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 41.66667%;
+            flex: 0 0 41.66667%;
+    max-width: 41.66667%;
+  }
+  .col-sm-6 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .col-sm-7 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 58.33333%;
+            flex: 0 0 58.33333%;
+    max-width: 58.33333%;
+  }
+  .col-sm-8 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 66.66667%;
+            flex: 0 0 66.66667%;
+    max-width: 66.66667%;
+  }
+  .col-sm-9 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 75%;
+            flex: 0 0 75%;
+    max-width: 75%;
+  }
+  .col-sm-10 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 83.33333%;
+            flex: 0 0 83.33333%;
+    max-width: 83.33333%;
+  }
+  .col-sm-11 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 91.66667%;
+            flex: 0 0 91.66667%;
+    max-width: 91.66667%;
+  }
+  .col-sm-12 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .order-sm-first {
+    -webkit-box-ordinal-group: 0;
+        -ms-flex-order: -1;
+            order: -1;
+  }
+  .order-sm-last {
+    -webkit-box-ordinal-group: 14;
+        -ms-flex-order: 13;
+            order: 13;
+  }
+  .order-sm-0 {
+    -webkit-box-ordinal-group: 1;
+        -ms-flex-order: 0;
+            order: 0;
+  }
+  .order-sm-1 {
+    -webkit-box-ordinal-group: 2;
+        -ms-flex-order: 1;
+            order: 1;
+  }
+  .order-sm-2 {
+    -webkit-box-ordinal-group: 3;
+        -ms-flex-order: 2;
+            order: 2;
+  }
+  .order-sm-3 {
+    -webkit-box-ordinal-group: 4;
+        -ms-flex-order: 3;
+            order: 3;
+  }
+  .order-sm-4 {
+    -webkit-box-ordinal-group: 5;
+        -ms-flex-order: 4;
+            order: 4;
+  }
+  .order-sm-5 {
+    -webkit-box-ordinal-group: 6;
+        -ms-flex-order: 5;
+            order: 5;
+  }
+  .order-sm-6 {
+    -webkit-box-ordinal-group: 7;
+        -ms-flex-order: 6;
+            order: 6;
+  }
+  .order-sm-7 {
+    -webkit-box-ordinal-group: 8;
+        -ms-flex-order: 7;
+            order: 7;
+  }
+  .order-sm-8 {
+    -webkit-box-ordinal-group: 9;
+        -ms-flex-order: 8;
+            order: 8;
+  }
+  .order-sm-9 {
+    -webkit-box-ordinal-group: 10;
+        -ms-flex-order: 9;
+            order: 9;
+  }
+  .order-sm-10 {
+    -webkit-box-ordinal-group: 11;
+        -ms-flex-order: 10;
+            order: 10;
+  }
+  .order-sm-11 {
+    -webkit-box-ordinal-group: 12;
+        -ms-flex-order: 11;
+            order: 11;
+  }
+  .order-sm-12 {
+    -webkit-box-ordinal-group: 13;
+        -ms-flex-order: 12;
+            order: 12;
+  }
+  .offset-sm-0 {
+    margin-left: 0;
+  }
+  .offset-sm-1 {
+    margin-left: 8.33333%;
+  }
+  .offset-sm-2 {
+    margin-left: 16.66667%;
+  }
+  .offset-sm-3 {
+    margin-left: 25%;
+  }
+  .offset-sm-4 {
+    margin-left: 33.33333%;
+  }
+  .offset-sm-5 {
+    margin-left: 41.66667%;
+  }
+  .offset-sm-6 {
+    margin-left: 50%;
+  }
+  .offset-sm-7 {
+    margin-left: 58.33333%;
+  }
+  .offset-sm-8 {
+    margin-left: 66.66667%;
+  }
+  .offset-sm-9 {
+    margin-left: 75%;
+  }
+  .offset-sm-10 {
+    margin-left: 83.33333%;
+  }
+  .offset-sm-11 {
+    margin-left: 91.66667%;
+  }
+}
+
+@media (min-width: 768px) {
+  .col-md {
+    -ms-flex-preferred-size: 0;
+        flex-basis: 0;
+    -webkit-box-flex: 1;
+        -ms-flex-positive: 1;
+            flex-grow: 1;
+    max-width: 100%;
+  }
+  .row-cols-md-1 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .row-cols-md-2 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .row-cols-md-3 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .row-cols-md-4 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .row-cols-md-5 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 20%;
+            flex: 0 0 20%;
+    max-width: 20%;
+  }
+  .row-cols-md-6 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-md-auto {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 auto;
+            flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+  .col-md-1 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 8.33333%;
+            flex: 0 0 8.33333%;
+    max-width: 8.33333%;
+  }
+  .col-md-2 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-md-3 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .col-md-4 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .col-md-5 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 41.66667%;
+            flex: 0 0 41.66667%;
+    max-width: 41.66667%;
+  }
+  .col-md-6 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .col-md-7 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 58.33333%;
+            flex: 0 0 58.33333%;
+    max-width: 58.33333%;
+  }
+  .col-md-8 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 66.66667%;
+            flex: 0 0 66.66667%;
+    max-width: 66.66667%;
+  }
+  .col-md-9 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 75%;
+            flex: 0 0 75%;
+    max-width: 75%;
+  }
+  .col-md-10 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 83.33333%;
+            flex: 0 0 83.33333%;
+    max-width: 83.33333%;
+  }
+  .col-md-11 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 91.66667%;
+            flex: 0 0 91.66667%;
+    max-width: 91.66667%;
+  }
+  .col-md-12 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .order-md-first {
+    -webkit-box-ordinal-group: 0;
+        -ms-flex-order: -1;
+            order: -1;
+  }
+  .order-md-last {
+    -webkit-box-ordinal-group: 14;
+        -ms-flex-order: 13;
+            order: 13;
+  }
+  .order-md-0 {
+    -webkit-box-ordinal-group: 1;
+        -ms-flex-order: 0;
+            order: 0;
+  }
+  .order-md-1 {
+    -webkit-box-ordinal-group: 2;
+        -ms-flex-order: 1;
+            order: 1;
+  }
+  .order-md-2 {
+    -webkit-box-ordinal-group: 3;
+        -ms-flex-order: 2;
+            order: 2;
+  }
+  .order-md-3 {
+    -webkit-box-ordinal-group: 4;
+        -ms-flex-order: 3;
+            order: 3;
+  }
+  .order-md-4 {
+    -webkit-box-ordinal-group: 5;
+        -ms-flex-order: 4;
+            order: 4;
+  }
+  .order-md-5 {
+    -webkit-box-ordinal-group: 6;
+        -ms-flex-order: 5;
+            order: 5;
+  }
+  .order-md-6 {
+    -webkit-box-ordinal-group: 7;
+        -ms-flex-order: 6;
+            order: 6;
+  }
+  .order-md-7 {
+    -webkit-box-ordinal-group: 8;
+        -ms-flex-order: 7;
+            order: 7;
+  }
+  .order-md-8 {
+    -webkit-box-ordinal-group: 9;
+        -ms-flex-order: 8;
+            order: 8;
+  }
+  .order-md-9 {
+    -webkit-box-ordinal-group: 10;
+        -ms-flex-order: 9;
+            order: 9;
+  }
+  .order-md-10 {
+    -webkit-box-ordinal-group: 11;
+        -ms-flex-order: 10;
+            order: 10;
+  }
+  .order-md-11 {
+    -webkit-box-ordinal-group: 12;
+        -ms-flex-order: 11;
+            order: 11;
+  }
+  .order-md-12 {
+    -webkit-box-ordinal-group: 13;
+        -ms-flex-order: 12;
+            order: 12;
+  }
+  .offset-md-0 {
+    margin-left: 0;
+  }
+  .offset-md-1 {
+    margin-left: 8.33333%;
+  }
+  .offset-md-2 {
+    margin-left: 16.66667%;
+  }
+  .offset-md-3 {
+    margin-left: 25%;
+  }
+  .offset-md-4 {
+    margin-left: 33.33333%;
+  }
+  .offset-md-5 {
+    margin-left: 41.66667%;
+  }
+  .offset-md-6 {
+    margin-left: 50%;
+  }
+  .offset-md-7 {
+    margin-left: 58.33333%;
+  }
+  .offset-md-8 {
+    margin-left: 66.66667%;
+  }
+  .offset-md-9 {
+    margin-left: 75%;
+  }
+  .offset-md-10 {
+    margin-left: 83.33333%;
+  }
+  .offset-md-11 {
+    margin-left: 91.66667%;
+  }
+}
+
+@media (min-width: 992px) {
+  .col-lg {
+    -ms-flex-preferred-size: 0;
+        flex-basis: 0;
+    -webkit-box-flex: 1;
+        -ms-flex-positive: 1;
+            flex-grow: 1;
+    max-width: 100%;
+  }
+  .row-cols-lg-1 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .row-cols-lg-2 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .row-cols-lg-3 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .row-cols-lg-4 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .row-cols-lg-5 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 20%;
+            flex: 0 0 20%;
+    max-width: 20%;
+  }
+  .row-cols-lg-6 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-lg-auto {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 auto;
+            flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+  .col-lg-1 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 8.33333%;
+            flex: 0 0 8.33333%;
+    max-width: 8.33333%;
+  }
+  .col-lg-2 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-lg-3 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .col-lg-4 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .col-lg-5 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 41.66667%;
+            flex: 0 0 41.66667%;
+    max-width: 41.66667%;
+  }
+  .col-lg-6 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .col-lg-7 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 58.33333%;
+            flex: 0 0 58.33333%;
+    max-width: 58.33333%;
+  }
+  .col-lg-8 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 66.66667%;
+            flex: 0 0 66.66667%;
+    max-width: 66.66667%;
+  }
+  .col-lg-9 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 75%;
+            flex: 0 0 75%;
+    max-width: 75%;
+  }
+  .col-lg-10 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 83.33333%;
+            flex: 0 0 83.33333%;
+    max-width: 83.33333%;
+  }
+  .col-lg-11 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 91.66667%;
+            flex: 0 0 91.66667%;
+    max-width: 91.66667%;
+  }
+  .col-lg-12 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .order-lg-first {
+    -webkit-box-ordinal-group: 0;
+        -ms-flex-order: -1;
+            order: -1;
+  }
+  .order-lg-last {
+    -webkit-box-ordinal-group: 14;
+        -ms-flex-order: 13;
+            order: 13;
+  }
+  .order-lg-0 {
+    -webkit-box-ordinal-group: 1;
+        -ms-flex-order: 0;
+            order: 0;
+  }
+  .order-lg-1 {
+    -webkit-box-ordinal-group: 2;
+        -ms-flex-order: 1;
+            order: 1;
+  }
+  .order-lg-2 {
+    -webkit-box-ordinal-group: 3;
+        -ms-flex-order: 2;
+            order: 2;
+  }
+  .order-lg-3 {
+    -webkit-box-ordinal-group: 4;
+        -ms-flex-order: 3;
+            order: 3;
+  }
+  .order-lg-4 {
+    -webkit-box-ordinal-group: 5;
+        -ms-flex-order: 4;
+            order: 4;
+  }
+  .order-lg-5 {
+    -webkit-box-ordinal-group: 6;
+        -ms-flex-order: 5;
+            order: 5;
+  }
+  .order-lg-6 {
+    -webkit-box-ordinal-group: 7;
+        -ms-flex-order: 6;
+            order: 6;
+  }
+  .order-lg-7 {
+    -webkit-box-ordinal-group: 8;
+        -ms-flex-order: 7;
+            order: 7;
+  }
+  .order-lg-8 {
+    -webkit-box-ordinal-group: 9;
+        -ms-flex-order: 8;
+            order: 8;
+  }
+  .order-lg-9 {
+    -webkit-box-ordinal-group: 10;
+        -ms-flex-order: 9;
+            order: 9;
+  }
+  .order-lg-10 {
+    -webkit-box-ordinal-group: 11;
+        -ms-flex-order: 10;
+            order: 10;
+  }
+  .order-lg-11 {
+    -webkit-box-ordinal-group: 12;
+        -ms-flex-order: 11;
+            order: 11;
+  }
+  .order-lg-12 {
+    -webkit-box-ordinal-group: 13;
+        -ms-flex-order: 12;
+            order: 12;
+  }
+  .offset-lg-0 {
+    margin-left: 0;
+  }
+  .offset-lg-1 {
+    margin-left: 8.33333%;
+  }
+  .offset-lg-2 {
+    margin-left: 16.66667%;
+  }
+  .offset-lg-3 {
+    margin-left: 25%;
+  }
+  .offset-lg-4 {
+    margin-left: 33.33333%;
+  }
+  .offset-lg-5 {
+    margin-left: 41.66667%;
+  }
+  .offset-lg-6 {
+    margin-left: 50%;
+  }
+  .offset-lg-7 {
+    margin-left: 58.33333%;
+  }
+  .offset-lg-8 {
+    margin-left: 66.66667%;
+  }
+  .offset-lg-9 {
+    margin-left: 75%;
+  }
+  .offset-lg-10 {
+    margin-left: 83.33333%;
+  }
+  .offset-lg-11 {
+    margin-left: 91.66667%;
+  }
+}
+
+@media (min-width: 1200px) {
+  .col-xl {
+    -ms-flex-preferred-size: 0;
+        flex-basis: 0;
+    -webkit-box-flex: 1;
+        -ms-flex-positive: 1;
+            flex-grow: 1;
+    max-width: 100%;
+  }
+  .row-cols-xl-1 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .row-cols-xl-2 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .row-cols-xl-3 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .row-cols-xl-4 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .row-cols-xl-5 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 20%;
+            flex: 0 0 20%;
+    max-width: 20%;
+  }
+  .row-cols-xl-6 > * {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-xl-auto {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 auto;
+            flex: 0 0 auto;
+    width: auto;
+    max-width: 100%;
+  }
+  .col-xl-1 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 8.33333%;
+            flex: 0 0 8.33333%;
+    max-width: 8.33333%;
+  }
+  .col-xl-2 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 16.66667%;
+            flex: 0 0 16.66667%;
+    max-width: 16.66667%;
+  }
+  .col-xl-3 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 25%;
+            flex: 0 0 25%;
+    max-width: 25%;
+  }
+  .col-xl-4 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 33.33333%;
+            flex: 0 0 33.33333%;
+    max-width: 33.33333%;
+  }
+  .col-xl-5 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 41.66667%;
+            flex: 0 0 41.66667%;
+    max-width: 41.66667%;
+  }
+  .col-xl-6 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 50%;
+            flex: 0 0 50%;
+    max-width: 50%;
+  }
+  .col-xl-7 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 58.33333%;
+            flex: 0 0 58.33333%;
+    max-width: 58.33333%;
+  }
+  .col-xl-8 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 66.66667%;
+            flex: 0 0 66.66667%;
+    max-width: 66.66667%;
+  }
+  .col-xl-9 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 75%;
+            flex: 0 0 75%;
+    max-width: 75%;
+  }
+  .col-xl-10 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 83.33333%;
+            flex: 0 0 83.33333%;
+    max-width: 83.33333%;
+  }
+  .col-xl-11 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 91.66667%;
+            flex: 0 0 91.66667%;
+    max-width: 91.66667%;
+  }
+  .col-xl-12 {
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 100%;
+            flex: 0 0 100%;
+    max-width: 100%;
+  }
+  .order-xl-first {
+    -webkit-box-ordinal-group: 0;
+        -ms-flex-order: -1;
+            order: -1;
+  }
+  .order-xl-last {
+    -webkit-box-ordinal-group: 14;
+        -ms-flex-order: 13;
+            order: 13;
+  }
+  .order-xl-0 {
+    -webkit-box-ordinal-group: 1;
+        -ms-flex-order: 0;
+            order: 0;
+  }
+  .order-xl-1 {
+    -webkit-box-ordinal-group: 2;
+        -ms-flex-order: 1;
+            order: 1;
+  }
+  .order-xl-2 {
+    -webkit-box-ordinal-group: 3;
+        -ms-flex-order: 2;
+            order: 2;
+  }
+  .order-xl-3 {
+    -webkit-box-ordinal-group: 4;
+        -ms-flex-order: 3;
+            order: 3;
+  }
+  .order-xl-4 {
+    -webkit-box-ordinal-group: 5;
+        -ms-flex-order: 4;
+            order: 4;
+  }
+  .order-xl-5 {
+    -webkit-box-ordinal-group: 6;
+        -ms-flex-order: 5;
+            order: 5;
+  }
+  .order-xl-6 {
+    -webkit-box-ordinal-group: 7;
+        -ms-flex-order: 6;
+            order: 6;
+  }
+  .order-xl-7 {
+    -webkit-box-ordinal-group: 8;
+        -ms-flex-order: 7;
+            order: 7;
+  }
+  .order-xl-8 {
+    -webkit-box-ordinal-group: 9;
+        -ms-flex-order: 8;
+            order: 8;
+  }
+  .order-xl-9 {
+    -webkit-box-ordinal-group: 10;
+        -ms-flex-order: 9;
+            order: 9;
+  }
+  .order-xl-10 {
+    -webkit-box-ordinal-group: 11;
+        -ms-flex-order: 10;
+            order: 10;
+  }
+  .order-xl-11 {
+    -webkit-box-ordinal-group: 12;
+        -ms-flex-order: 11;
+            order: 11;
+  }
+  .order-xl-12 {
+    -webkit-box-ordinal-group: 13;
+        -ms-flex-order: 12;
+            order: 12;
+  }
+  .offset-xl-0 {
+    margin-left: 0;
+  }
+  .offset-xl-1 {
+    margin-left: 8.33333%;
+  }
+  .offset-xl-2 {
+    margin-left: 16.66667%;
+  }
+  .offset-xl-3 {
+    margin-left: 25%;
+  }
+  .offset-xl-4 {
+    margin-left: 33.33333%;
+  }
+  .offset-xl-5 {
+    margin-left: 41.66667%;
+  }
+  .offset-xl-6 {
+    margin-left: 50%;
+  }
+  .offset-xl-7 {
+    margin-left: 58.33333%;
+  }
+  .offset-xl-8 {
+    margin-left: 66.66667%;
+  }
+  .offset-xl-9 {
+    margin-left: 75%;
+  }
+  .offset-xl-10 {
+    margin-left: 83.33333%;
+  }
+  .offset-xl-11 {
+    margin-left: 91.66667%;
+  }
+}
+
+.table {
+  width: 100%;
+  margin-bottom: 1rem;
+  color: #212529;
+}
+
+.table th,
+.table td {
+  padding: 0.75rem;
+  vertical-align: top;
+  border-top: 1px solid #dee2e6;
+}
+
+.table thead th {
+  vertical-align: bottom;
+  border-bottom: 2px solid #dee2e6;
+}
+
+.table tbody + tbody {
+  border-top: 2px solid #dee2e6;
+}
+
+.table-sm th,
+.table-sm td {
+  padding: 0.3rem;
+}
+
+.table-bordered {
+  border: 1px solid #dee2e6;
+}
+
+.table-bordered th,
+.table-bordered td {
+  border: 1px solid #dee2e6;
+}
+
+.table-bordered thead th,
+.table-bordered thead td {
+  border-bottom-width: 2px;
+}
+
+.table-borderless th,
+.table-borderless td,
+.table-borderless thead th,
+.table-borderless tbody + tbody {
+  border: 0;
+}
+
+.table-striped tbody tr:nth-of-type(odd) {
+  background-color: rgba(0, 0, 0, 0.05);
+}
+
+.table-hover tbody tr:hover {
+  color: #212529;
+  background-color: rgba(0, 0, 0, 0.075);
+}
+
+.table-primary,
+.table-primary > th,
+.table-primary > td {
+  background-color: #b8daff;
+}
+
+.table-primary th,
+.table-primary td,
+.table-primary thead th,
+.table-primary tbody + tbody {
+  border-color: #7abaff;
+}
+
+.table-hover .table-primary:hover {
+  background-color: #9fcdff;
+}
+
+.table-hover .table-primary:hover > td,
+.table-hover .table-primary:hover > th {
+  background-color: #9fcdff;
+}
+
+.table-secondary,
+.table-secondary > th,
+.table-secondary > td {
+  background-color: #d6d8db;
+}
+
+.table-secondary th,
+.table-secondary td,
+.table-secondary thead th,
+.table-secondary tbody + tbody {
+  border-color: #b3b7bb;
+}
+
+.table-hover .table-secondary:hover {
+  background-color: #c8cbcf;
+}
+
+.table-hover .table-secondary:hover > td,
+.table-hover .table-secondary:hover > th {
+  background-color: #c8cbcf;
+}
+
+.table-success,
+.table-success > th,
+.table-success > td {
+  background-color: #c3e6cb;
+}
+
+.table-success th,
+.table-success td,
+.table-success thead th,
+.table-success tbody + tbody {
+  border-color: #8fd19e;
+}
+
+.table-hover .table-success:hover {
+  background-color: #b1dfbb;
+}
+
+.table-hover .table-success:hover > td,
+.table-hover .table-success:hover > th {
+  background-color: #b1dfbb;
+}
+
+.table-info,
+.table-info > th,
+.table-info > td {
+  background-color: #bee5eb;
+}
+
+.table-info th,
+.table-info td,
+.table-info thead th,
+.table-info tbody + tbody {
+  border-color: #86cfda;
+}
+
+.table-hover .table-info:hover {
+  background-color: #abdde5;
+}
+
+.table-hover .table-info:hover > td,
+.table-hover .table-info:hover > th {
+  background-color: #abdde5;
+}
+
+.table-warning,
+.table-warning > th,
+.table-warning > td {
+  background-color: #ffeeba;
+}
+
+.table-warning th,
+.table-warning td,
+.table-warning thead th,
+.table-warning tbody + tbody {
+  border-color: #ffdf7e;
+}
+
+.table-hover .table-warning:hover {
+  background-color: #ffe8a1;
+}
+
+.table-hover .table-warning:hover > td,
+.table-hover .table-warning:hover > th {
+  background-color: #ffe8a1;
+}
+
+.table-danger,
+.table-danger > th,
+.table-danger > td {
+  background-color: #f5c6cb;
+}
+
+.table-danger th,
+.table-danger td,
+.table-danger thead th,
+.table-danger tbody + tbody {
+  border-color: #ed969e;
+}
+
+.table-hover .table-danger:hover {
+  background-color: #f1b0b7;
+}
+
+.table-hover .table-danger:hover > td,
+.table-hover .table-danger:hover > th {
+  background-color: #f1b0b7;
+}
+
+.table-light,
+.table-light > th,
+.table-light > td {
+  background-color: #fdfdfe;
+}
+
+.table-light th,
+.table-light td,
+.table-light thead th,
+.table-light tbody + tbody {
+  border-color: #fbfcfc;
+}
+
+.table-hover .table-light:hover {
+  background-color: #ececf6;
+}
+
+.table-hover .table-light:hover > td,
+.table-hover .table-light:hover > th {
+  background-color: #ececf6;
+}
+
+.table-dark,
+.table-dark > th,
+.table-dark > td {
+  background-color: #c6c8ca;
+}
+
+.table-dark th,
+.table-dark td,
+.table-dark thead th,
+.table-dark tbody + tbody {
+  border-color: #95999c;
+}
+
+.table-hover .table-dark:hover {
+  background-color: #b9bbbe;
+}
+
+.table-hover .table-dark:hover > td,
+.table-hover .table-dark:hover > th {
+  background-color: #b9bbbe;
+}
+
+.table-active,
+.table-active > th,
+.table-active > td {
+  background-color: rgba(0, 0, 0, 0.075);
+}
+
+.table-hover .table-active:hover {
+  background-color: rgba(0, 0, 0, 0.075);
+}
+
+.table-hover .table-active:hover > td,
+.table-hover .table-active:hover > th {
+  background-color: rgba(0, 0, 0, 0.075);
+}
+
+.table .thead-dark th {
+  color: #fff;
+  background-color: #343a40;
+  border-color: #454d55;
+}
+
+.table .thead-light th {
+  color: #495057;
+  background-color: #e9ecef;
+  border-color: #dee2e6;
+}
+
+.table-dark {
+  color: #fff;
+  background-color: #343a40;
+}
+
+.table-dark th,
+.table-dark td,
+.table-dark thead th {
+  border-color: #454d55;
+}
+
+.table-dark.table-bordered {
+  border: 0;
+}
+
+.table-dark.table-striped tbody tr:nth-of-type(odd) {
+  background-color: rgba(255, 255, 255, 0.05);
+}
+
+.table-dark.table-hover tbody tr:hover {
+  color: #fff;
+  background-color: rgba(255, 255, 255, 0.075);
+}
+
+@media (max-width: 575.98px) {
+  .table-responsive-sm {
+    display: block;
+    width: 100%;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+  }
+  .table-responsive-sm > .table-bordered {
+    border: 0;
+  }
+}
+
+@media (max-width: 767.98px) {
+  .table-responsive-md {
+    display: block;
+    width: 100%;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+  }
+  .table-responsive-md > .table-bordered {
+    border: 0;
+  }
+}
+
+@media (max-width: 991.98px) {
+  .table-responsive-lg {
+    display: block;
+    width: 100%;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+  }
+  .table-responsive-lg > .table-bordered {
+    border: 0;
+  }
+}
+
+@media (max-width: 1199.98px) {
+  .table-responsive-xl {
+    display: block;
+    width: 100%;
+    overflow-x: auto;
+    -webkit-overflow-scrolling: touch;
+  }
+  .table-responsive-xl > .table-bordered {
+    border: 0;
+  }
+}
+
+.table-responsive {
+  display: block;
+  width: 100%;
+  overflow-x: auto;
+  -webkit-overflow-scrolling: touch;
+}
+
+.table-responsive > .table-bordered {
+  border: 0;
+}
+
+.form-control {
+  display: block;
+  width: 100%;
+  height: calc(1.5em + 0.75rem + 2px);
+  padding: 0.375rem 0.75rem;
+  font-size: 1rem;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #495057;
+  background-color: #fff;
+  background-clip: padding-box;
+  border: 1px solid #ced4da;
+  border-radius: 0.25rem;
+  -webkit-transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .form-control {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.form-control::-ms-expand {
+  background-color: transparent;
+  border: 0;
+}
+
+.form-control:-moz-focusring {
+  color: transparent;
+  text-shadow: 0 0 0 #495057;
+}
+
+.form-control:focus {
+  color: #495057;
+  background-color: #fff;
+  border-color: #80bdff;
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.form-control::-webkit-input-placeholder {
+  color: #6c757d;
+  opacity: 1;
+}
+
+.form-control:-ms-input-placeholder {
+  color: #6c757d;
+  opacity: 1;
+}
+
+.form-control::-ms-input-placeholder {
+  color: #6c757d;
+  opacity: 1;
+}
+
+.form-control::placeholder {
+  color: #6c757d;
+  opacity: 1;
+}
+
+.form-control:disabled, .form-control[readonly] {
+  background-color: #e9ecef;
+  opacity: 1;
+}
+
+input[type="date"].form-control,
+input[type="time"].form-control,
+input[type="datetime-local"].form-control,
+input[type="month"].form-control {
+  -webkit-appearance: none;
+     -moz-appearance: none;
+          appearance: none;
+}
+
+select.form-control:focus::-ms-value {
+  color: #495057;
+  background-color: #fff;
+}
+
+.form-control-file,
+.form-control-range {
+  display: block;
+  width: 100%;
+}
+
+.col-form-label {
+  padding-top: calc(0.375rem + 1px);
+  padding-bottom: calc(0.375rem + 1px);
+  margin-bottom: 0;
+  font-size: inherit;
+  line-height: 1.5;
+}
+
+.col-form-label-lg {
+  padding-top: calc(0.5rem + 1px);
+  padding-bottom: calc(0.5rem + 1px);
+  font-size: 1.25rem;
+  line-height: 1.5;
+}
+
+.col-form-label-sm {
+  padding-top: calc(0.25rem + 1px);
+  padding-bottom: calc(0.25rem + 1px);
+  font-size: 0.875rem;
+  line-height: 1.5;
+}
+
+.form-control-plaintext {
+  display: block;
+  width: 100%;
+  padding: 0.375rem 0;
+  margin-bottom: 0;
+  font-size: 1rem;
+  line-height: 1.5;
+  color: #212529;
+  background-color: transparent;
+  border: solid transparent;
+  border-width: 1px 0;
+}
+
+.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.form-control-sm {
+  height: calc(1.5em + 0.5rem + 2px);
+  padding: 0.25rem 0.5rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+  border-radius: 0.2rem;
+}
+
+.form-control-lg {
+  height: calc(1.5em + 1rem + 2px);
+  padding: 0.5rem 1rem;
+  font-size: 1.25rem;
+  line-height: 1.5;
+  border-radius: 0.3rem;
+}
+
+select.form-control[size], select.form-control[multiple] {
+  height: auto;
+}
+
+textarea.form-control {
+  height: auto;
+}
+
+.form-group {
+  margin-bottom: 1rem;
+}
+
+.form-text {
+  display: block;
+  margin-top: 0.25rem;
+}
+
+.form-row {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  margin-right: -5px;
+  margin-left: -5px;
+}
+
+.form-row > .col,
+.form-row > [class*="col-"] {
+  padding-right: 5px;
+  padding-left: 5px;
+}
+
+.form-check {
+  position: relative;
+  display: block;
+  padding-left: 1.25rem;
+}
+
+.form-check-input {
+  position: absolute;
+  margin-top: 0.3rem;
+  margin-left: -1.25rem;
+}
+
+.form-check-input[disabled] ~ .form-check-label,
+.form-check-input:disabled ~ .form-check-label {
+  color: #6c757d;
+}
+
+.form-check-label {
+  margin-bottom: 0;
+}
+
+.form-check-inline {
+  display: -webkit-inline-box;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+  padding-left: 0;
+  margin-right: 0.75rem;
+}
+
+.form-check-inline .form-check-input {
+  position: static;
+  margin-top: 0;
+  margin-right: 0.3125rem;
+  margin-left: 0;
+}
+
+.valid-feedback {
+  display: none;
+  width: 100%;
+  margin-top: 0.25rem;
+  font-size: 80%;
+  color: #28a745;
+}
+
+.valid-tooltip {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 5;
+  display: none;
+  max-width: 100%;
+  padding: 0.25rem 0.5rem;
+  margin-top: .1rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+  color: #fff;
+  background-color: rgba(40, 167, 69, 0.9);
+  border-radius: 0.25rem;
+}
+
+.form-row > .col > .valid-tooltip,
+.form-row > [class*="col-"] > .valid-tooltip {
+  left: 5px;
+}
+
+.was-validated :valid ~ .valid-feedback,
+.was-validated :valid ~ .valid-tooltip,
+.is-valid ~ .valid-feedback,
+.is-valid ~ .valid-tooltip {
+  display: block;
+}
+
+.was-validated .form-control:valid, .form-control.is-valid {
+  border-color: #28a745;
+  padding-right: calc(1.5em + 0.75rem);
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");
+  background-repeat: no-repeat;
+  background-position: right calc(0.375em + 0.1875rem) center;
+  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+}
+
+.was-validated .form-control:valid:focus, .form-control.is-valid:focus {
+  border-color: #28a745;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+}
+
+.was-validated textarea.form-control:valid, textarea.form-control.is-valid {
+  padding-right: calc(1.5em + 0.75rem);
+  background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);
+}
+
+.was-validated .custom-select:valid, .custom-select.is-valid {
+  border-color: #28a745;
+  padding-right: calc(0.75em + 2.3125rem);
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat, #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) no-repeat;
+}
+
+.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {
+  border-color: #28a745;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+}
+
+.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {
+  color: #28a745;
+}
+
+.was-validated .form-check-input:valid ~ .valid-feedback,
+.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,
+.form-check-input.is-valid ~ .valid-tooltip {
+  display: block;
+}
+
+.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {
+  color: #28a745;
+}
+
+.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {
+  border-color: #28a745;
+}
+
+.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {
+  border-color: #34ce57;
+  background-color: #34ce57;
+}
+
+.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+}
+
+.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {
+  border-color: #28a745;
+}
+
+.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {
+  border-color: #28a745;
+}
+
+.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {
+  border-color: #28a745;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);
+}
+
+.invalid-feedback {
+  display: none;
+  width: 100%;
+  margin-top: 0.25rem;
+  font-size: 80%;
+  color: #dc3545;
+}
+
+.invalid-tooltip {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 5;
+  display: none;
+  max-width: 100%;
+  padding: 0.25rem 0.5rem;
+  margin-top: .1rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+  color: #fff;
+  background-color: rgba(220, 53, 69, 0.9);
+  border-radius: 0.25rem;
+}
+
+.form-row > .col > .invalid-tooltip,
+.form-row > [class*="col-"] > .invalid-tooltip {
+  left: 5px;
+}
+
+.was-validated :invalid ~ .invalid-feedback,
+.was-validated :invalid ~ .invalid-tooltip,
+.is-invalid ~ .invalid-feedback,
+.is-invalid ~ .invalid-tooltip {
+  display: block;
+}
+
+.was-validated .form-control:invalid, .form-control.is-invalid {
+  border-color: #dc3545;
+  padding-right: calc(1.5em + 0.75rem);
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");
+  background-repeat: no-repeat;
+  background-position: right calc(0.375em + 0.1875rem) center;
+  background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);
+}
+
+.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {
+  border-color: #dc3545;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
+
+.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {
+  padding-right: calc(1.5em + 0.75rem);
+  background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);
+}
+
+.was-validated .custom-select:invalid, .custom-select.is-invalid {
+  border-color: #dc3545;
+  padding-right: calc(0.75em + 2.3125rem);
+  background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat, #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem) no-repeat;
+}
+
+.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {
+  border-color: #dc3545;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
+
+.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {
+  color: #dc3545;
+}
+
+.was-validated .form-check-input:invalid ~ .invalid-feedback,
+.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,
+.form-check-input.is-invalid ~ .invalid-tooltip {
+  display: block;
+}
+
+.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {
+  color: #dc3545;
+}
+
+.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {
+  border-color: #dc3545;
+}
+
+.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {
+  border-color: #e4606d;
+  background-color: #e4606d;
+}
+
+.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
+
+.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {
+  border-color: #dc3545;
+}
+
+.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {
+  border-color: #dc3545;
+}
+
+.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {
+  border-color: #dc3545;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);
+}
+
+.form-inline {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: horizontal;
+  -webkit-box-direction: normal;
+      -ms-flex-flow: row wrap;
+          flex-flow: row wrap;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+}
+
+.form-inline .form-check {
+  width: 100%;
+}
+
+@media (min-width: 576px) {
+  .form-inline label {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-align: center;
+        -ms-flex-align: center;
+            align-items: center;
+    -webkit-box-pack: center;
+        -ms-flex-pack: center;
+            justify-content: center;
+    margin-bottom: 0;
+  }
+  .form-inline .form-group {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-flex: 0;
+        -ms-flex: 0 0 auto;
+            flex: 0 0 auto;
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-flow: row wrap;
+            flex-flow: row wrap;
+    -webkit-box-align: center;
+        -ms-flex-align: center;
+            align-items: center;
+    margin-bottom: 0;
+  }
+  .form-inline .form-control {
+    display: inline-block;
+    width: auto;
+    vertical-align: middle;
+  }
+  .form-inline .form-control-plaintext {
+    display: inline-block;
+  }
+  .form-inline .input-group,
+  .form-inline .custom-select {
+    width: auto;
+  }
+  .form-inline .form-check {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-align: center;
+        -ms-flex-align: center;
+            align-items: center;
+    -webkit-box-pack: center;
+        -ms-flex-pack: center;
+            justify-content: center;
+    width: auto;
+    padding-left: 0;
+  }
+  .form-inline .form-check-input {
+    position: relative;
+    -ms-flex-negative: 0;
+        flex-shrink: 0;
+    margin-top: 0;
+    margin-right: 0.25rem;
+    margin-left: 0;
+  }
+  .form-inline .custom-control {
+    -webkit-box-align: center;
+        -ms-flex-align: center;
+            align-items: center;
+    -webkit-box-pack: center;
+        -ms-flex-pack: center;
+            justify-content: center;
+  }
+  .form-inline .custom-control-label {
+    margin-bottom: 0;
+  }
+}
+
+.btn {
+  display: inline-block;
+  font-weight: 400;
+  color: #212529;
+  text-align: center;
+  vertical-align: middle;
+  -webkit-user-select: none;
+     -moz-user-select: none;
+      -ms-user-select: none;
+          user-select: none;
+  background-color: transparent;
+  border: 1px solid transparent;
+  padding: 0.375rem 0.75rem;
+  font-size: 1rem;
+  line-height: 1.5;
+  border-radius: 0.25rem;
+  -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .btn {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.btn:hover {
+  color: #212529;
+  text-decoration: none;
+}
+
+.btn:focus, .btn.focus {
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.btn.disabled, .btn:disabled {
+  opacity: 0.65;
+}
+
+.btn:not(:disabled):not(.disabled) {
+  cursor: pointer;
+}
+
+a.btn.disabled,
+fieldset:disabled a.btn {
+  pointer-events: none;
+}
+
+.btn-primary {
+  color: #fff;
+  background-color: #007bff;
+  border-color: #007bff;
+}
+
+.btn-primary:hover {
+  color: #fff;
+  background-color: #0069d9;
+  border-color: #0062cc;
+}
+
+.btn-primary:focus, .btn-primary.focus {
+  color: #fff;
+  background-color: #0069d9;
+  border-color: #0062cc;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);
+}
+
+.btn-primary.disabled, .btn-primary:disabled {
+  color: #fff;
+  background-color: #007bff;
+  border-color: #007bff;
+}
+
+.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,
+.show > .btn-primary.dropdown-toggle {
+  color: #fff;
+  background-color: #0062cc;
+  border-color: #005cbf;
+}
+
+.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,
+.show > .btn-primary.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);
+}
+
+.btn-secondary {
+  color: #fff;
+  background-color: #6c757d;
+  border-color: #6c757d;
+}
+
+.btn-secondary:hover {
+  color: #fff;
+  background-color: #5a6268;
+  border-color: #545b62;
+}
+
+.btn-secondary:focus, .btn-secondary.focus {
+  color: #fff;
+  background-color: #5a6268;
+  border-color: #545b62;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);
+}
+
+.btn-secondary.disabled, .btn-secondary:disabled {
+  color: #fff;
+  background-color: #6c757d;
+  border-color: #6c757d;
+}
+
+.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,
+.show > .btn-secondary.dropdown-toggle {
+  color: #fff;
+  background-color: #545b62;
+  border-color: #4e555b;
+}
+
+.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,
+.show > .btn-secondary.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);
+}
+
+.btn-success {
+  color: #fff;
+  background-color: #28a745;
+  border-color: #28a745;
+}
+
+.btn-success:hover {
+  color: #fff;
+  background-color: #218838;
+  border-color: #1e7e34;
+}
+
+.btn-success:focus, .btn-success.focus {
+  color: #fff;
+  background-color: #218838;
+  border-color: #1e7e34;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);
+}
+
+.btn-success.disabled, .btn-success:disabled {
+  color: #fff;
+  background-color: #28a745;
+  border-color: #28a745;
+}
+
+.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,
+.show > .btn-success.dropdown-toggle {
+  color: #fff;
+  background-color: #1e7e34;
+  border-color: #1c7430;
+}
+
+.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,
+.show > .btn-success.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);
+}
+
+.btn-info {
+  color: #fff;
+  background-color: #17a2b8;
+  border-color: #17a2b8;
+}
+
+.btn-info:hover {
+  color: #fff;
+  background-color: #138496;
+  border-color: #117a8b;
+}
+
+.btn-info:focus, .btn-info.focus {
+  color: #fff;
+  background-color: #138496;
+  border-color: #117a8b;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);
+}
+
+.btn-info.disabled, .btn-info:disabled {
+  color: #fff;
+  background-color: #17a2b8;
+  border-color: #17a2b8;
+}
+
+.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,
+.show > .btn-info.dropdown-toggle {
+  color: #fff;
+  background-color: #117a8b;
+  border-color: #10707f;
+}
+
+.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,
+.show > .btn-info.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);
+}
+
+.btn-warning {
+  color: #212529;
+  background-color: #ffc107;
+  border-color: #ffc107;
+}
+
+.btn-warning:hover {
+  color: #212529;
+  background-color: #e0a800;
+  border-color: #d39e00;
+}
+
+.btn-warning:focus, .btn-warning.focus {
+  color: #212529;
+  background-color: #e0a800;
+  border-color: #d39e00;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);
+}
+
+.btn-warning.disabled, .btn-warning:disabled {
+  color: #212529;
+  background-color: #ffc107;
+  border-color: #ffc107;
+}
+
+.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,
+.show > .btn-warning.dropdown-toggle {
+  color: #212529;
+  background-color: #d39e00;
+  border-color: #c69500;
+}
+
+.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,
+.show > .btn-warning.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);
+}
+
+.btn-danger {
+  color: #fff;
+  background-color: #dc3545;
+  border-color: #dc3545;
+}
+
+.btn-danger:hover {
+  color: #fff;
+  background-color: #c82333;
+  border-color: #bd2130;
+}
+
+.btn-danger:focus, .btn-danger.focus {
+  color: #fff;
+  background-color: #c82333;
+  border-color: #bd2130;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);
+}
+
+.btn-danger.disabled, .btn-danger:disabled {
+  color: #fff;
+  background-color: #dc3545;
+  border-color: #dc3545;
+}
+
+.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,
+.show > .btn-danger.dropdown-toggle {
+  color: #fff;
+  background-color: #bd2130;
+  border-color: #b21f2d;
+}
+
+.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,
+.show > .btn-danger.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);
+}
+
+.btn-light {
+  color: #212529;
+  background-color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+
+.btn-light:hover {
+  color: #212529;
+  background-color: #e2e6ea;
+  border-color: #dae0e5;
+}
+
+.btn-light:focus, .btn-light.focus {
+  color: #212529;
+  background-color: #e2e6ea;
+  border-color: #dae0e5;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);
+}
+
+.btn-light.disabled, .btn-light:disabled {
+  color: #212529;
+  background-color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+
+.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,
+.show > .btn-light.dropdown-toggle {
+  color: #212529;
+  background-color: #dae0e5;
+  border-color: #d3d9df;
+}
+
+.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,
+.show > .btn-light.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);
+}
+
+.btn-dark {
+  color: #fff;
+  background-color: #343a40;
+  border-color: #343a40;
+}
+
+.btn-dark:hover {
+  color: #fff;
+  background-color: #23272b;
+  border-color: #1d2124;
+}
+
+.btn-dark:focus, .btn-dark.focus {
+  color: #fff;
+  background-color: #23272b;
+  border-color: #1d2124;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);
+}
+
+.btn-dark.disabled, .btn-dark:disabled {
+  color: #fff;
+  background-color: #343a40;
+  border-color: #343a40;
+}
+
+.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,
+.show > .btn-dark.dropdown-toggle {
+  color: #fff;
+  background-color: #1d2124;
+  border-color: #171a1d;
+}
+
+.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,
+.show > .btn-dark.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);
+}
+
+.btn-outline-primary {
+  color: #007bff;
+  border-color: #007bff;
+}
+
+.btn-outline-primary:hover {
+  color: #fff;
+  background-color: #007bff;
+  border-color: #007bff;
+}
+
+.btn-outline-primary:focus, .btn-outline-primary.focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);
+}
+
+.btn-outline-primary.disabled, .btn-outline-primary:disabled {
+  color: #007bff;
+  background-color: transparent;
+}
+
+.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,
+.show > .btn-outline-primary.dropdown-toggle {
+  color: #fff;
+  background-color: #007bff;
+  border-color: #007bff;
+}
+
+.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-primary.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);
+}
+
+.btn-outline-secondary {
+  color: #6c757d;
+  border-color: #6c757d;
+}
+
+.btn-outline-secondary:hover {
+  color: #fff;
+  background-color: #6c757d;
+  border-color: #6c757d;
+}
+
+.btn-outline-secondary:focus, .btn-outline-secondary.focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);
+}
+
+.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {
+  color: #6c757d;
+  background-color: transparent;
+}
+
+.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,
+.show > .btn-outline-secondary.dropdown-toggle {
+  color: #fff;
+  background-color: #6c757d;
+  border-color: #6c757d;
+}
+
+.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-secondary.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);
+}
+
+.btn-outline-success {
+  color: #28a745;
+  border-color: #28a745;
+}
+
+.btn-outline-success:hover {
+  color: #fff;
+  background-color: #28a745;
+  border-color: #28a745;
+}
+
+.btn-outline-success:focus, .btn-outline-success.focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+
+.btn-outline-success.disabled, .btn-outline-success:disabled {
+  color: #28a745;
+  background-color: transparent;
+}
+
+.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,
+.show > .btn-outline-success.dropdown-toggle {
+  color: #fff;
+  background-color: #28a745;
+  border-color: #28a745;
+}
+
+.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-success.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+
+.btn-outline-info {
+  color: #17a2b8;
+  border-color: #17a2b8;
+}
+
+.btn-outline-info:hover {
+  color: #fff;
+  background-color: #17a2b8;
+  border-color: #17a2b8;
+}
+
+.btn-outline-info:focus, .btn-outline-info.focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+
+.btn-outline-info.disabled, .btn-outline-info:disabled {
+  color: #17a2b8;
+  background-color: transparent;
+}
+
+.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,
+.show > .btn-outline-info.dropdown-toggle {
+  color: #fff;
+  background-color: #17a2b8;
+  border-color: #17a2b8;
+}
+
+.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-info.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+
+.btn-outline-warning {
+  color: #ffc107;
+  border-color: #ffc107;
+}
+
+.btn-outline-warning:hover {
+  color: #212529;
+  background-color: #ffc107;
+  border-color: #ffc107;
+}
+
+.btn-outline-warning:focus, .btn-outline-warning.focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+}
+
+.btn-outline-warning.disabled, .btn-outline-warning:disabled {
+  color: #ffc107;
+  background-color: transparent;
+}
+
+.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,
+.show > .btn-outline-warning.dropdown-toggle {
+  color: #212529;
+  background-color: #ffc107;
+  border-color: #ffc107;
+}
+
+.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-warning.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+}
+
+.btn-outline-danger {
+  color: #dc3545;
+  border-color: #dc3545;
+}
+
+.btn-outline-danger:hover {
+  color: #fff;
+  background-color: #dc3545;
+  border-color: #dc3545;
+}
+
+.btn-outline-danger:focus, .btn-outline-danger.focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+
+.btn-outline-danger.disabled, .btn-outline-danger:disabled {
+  color: #dc3545;
+  background-color: transparent;
+}
+
+.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,
+.show > .btn-outline-danger.dropdown-toggle {
+  color: #fff;
+  background-color: #dc3545;
+  border-color: #dc3545;
+}
+
+.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-danger.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+
+.btn-outline-light {
+  color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+
+.btn-outline-light:hover {
+  color: #212529;
+  background-color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+
+.btn-outline-light:focus, .btn-outline-light.focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+
+.btn-outline-light.disabled, .btn-outline-light:disabled {
+  color: #f8f9fa;
+  background-color: transparent;
+}
+
+.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,
+.show > .btn-outline-light.dropdown-toggle {
+  color: #212529;
+  background-color: #f8f9fa;
+  border-color: #f8f9fa;
+}
+
+.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-light.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+
+.btn-outline-dark {
+  color: #343a40;
+  border-color: #343a40;
+}
+
+.btn-outline-dark:hover {
+  color: #fff;
+  background-color: #343a40;
+  border-color: #343a40;
+}
+
+.btn-outline-dark:focus, .btn-outline-dark.focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+
+.btn-outline-dark.disabled, .btn-outline-dark:disabled {
+  color: #343a40;
+  background-color: transparent;
+}
+
+.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,
+.show > .btn-outline-dark.dropdown-toggle {
+  color: #fff;
+  background-color: #343a40;
+  border-color: #343a40;
+}
+
+.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,
+.show > .btn-outline-dark.dropdown-toggle:focus {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+
+.btn-link {
+  font-weight: 400;
+  color: #007bff;
+  text-decoration: none;
+}
+
+.btn-link:hover {
+  color: #0056b3;
+  text-decoration: underline;
+}
+
+.btn-link:focus, .btn-link.focus {
+  text-decoration: underline;
+}
+
+.btn-link:disabled, .btn-link.disabled {
+  color: #6c757d;
+  pointer-events: none;
+}
+
+.btn-lg, .btn-group-lg > .btn {
+  padding: 0.5rem 1rem;
+  font-size: 1.25rem;
+  line-height: 1.5;
+  border-radius: 0.3rem;
+}
+
+.btn-sm, .btn-group-sm > .btn {
+  padding: 0.25rem 0.5rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+  border-radius: 0.2rem;
+}
+
+.btn-block {
+  display: block;
+  width: 100%;
+}
+
+.btn-block + .btn-block {
+  margin-top: 0.5rem;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+  width: 100%;
+}
+
+.fade {
+  -webkit-transition: opacity 0.15s linear;
+  transition: opacity 0.15s linear;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .fade {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.fade:not(.show) {
+  opacity: 0;
+}
+
+.collapse:not(.show) {
+  display: none;
+}
+
+.collapsing {
+  position: relative;
+  height: 0;
+  overflow: hidden;
+  -webkit-transition: height 0.35s ease;
+  transition: height 0.35s ease;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .collapsing {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.dropup,
+.dropright,
+.dropdown,
+.dropleft {
+  position: relative;
+}
+
+.dropdown-toggle {
+  white-space: nowrap;
+}
+
+.dropdown-toggle::after {
+  display: inline-block;
+  margin-left: 0.255em;
+  vertical-align: 0.255em;
+  content: "";
+  border-top: 0.3em solid;
+  border-right: 0.3em solid transparent;
+  border-bottom: 0;
+  border-left: 0.3em solid transparent;
+}
+
+.dropdown-toggle:empty::after {
+  margin-left: 0;
+}
+
+.dropdown-menu {
+  position: absolute;
+  top: 100%;
+  left: 0;
+  z-index: 1000;
+  display: none;
+  float: left;
+  min-width: 10rem;
+  padding: 0.5rem 0;
+  margin: 0.125rem 0 0;
+  font-size: 1rem;
+  color: #212529;
+  text-align: left;
+  list-style: none;
+  background-color: #fff;
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.15);
+  border-radius: 0.25rem;
+}
+
+.dropdown-menu-left {
+  right: auto;
+  left: 0;
+}
+
+.dropdown-menu-right {
+  right: 0;
+  left: auto;
+}
+
+@media (min-width: 576px) {
+  .dropdown-menu-sm-left {
+    right: auto;
+    left: 0;
+  }
+  .dropdown-menu-sm-right {
+    right: 0;
+    left: auto;
+  }
+}
+
+@media (min-width: 768px) {
+  .dropdown-menu-md-left {
+    right: auto;
+    left: 0;
+  }
+  .dropdown-menu-md-right {
+    right: 0;
+    left: auto;
+  }
+}
+
+@media (min-width: 992px) {
+  .dropdown-menu-lg-left {
+    right: auto;
+    left: 0;
+  }
+  .dropdown-menu-lg-right {
+    right: 0;
+    left: auto;
+  }
+}
+
+@media (min-width: 1200px) {
+  .dropdown-menu-xl-left {
+    right: auto;
+    left: 0;
+  }
+  .dropdown-menu-xl-right {
+    right: 0;
+    left: auto;
+  }
+}
+
+.dropup .dropdown-menu {
+  top: auto;
+  bottom: 100%;
+  margin-top: 0;
+  margin-bottom: 0.125rem;
+}
+
+.dropup .dropdown-toggle::after {
+  display: inline-block;
+  margin-left: 0.255em;
+  vertical-align: 0.255em;
+  content: "";
+  border-top: 0;
+  border-right: 0.3em solid transparent;
+  border-bottom: 0.3em solid;
+  border-left: 0.3em solid transparent;
+}
+
+.dropup .dropdown-toggle:empty::after {
+  margin-left: 0;
+}
+
+.dropright .dropdown-menu {
+  top: 0;
+  right: auto;
+  left: 100%;
+  margin-top: 0;
+  margin-left: 0.125rem;
+}
+
+.dropright .dropdown-toggle::after {
+  display: inline-block;
+  margin-left: 0.255em;
+  vertical-align: 0.255em;
+  content: "";
+  border-top: 0.3em solid transparent;
+  border-right: 0;
+  border-bottom: 0.3em solid transparent;
+  border-left: 0.3em solid;
+}
+
+.dropright .dropdown-toggle:empty::after {
+  margin-left: 0;
+}
+
+.dropright .dropdown-toggle::after {
+  vertical-align: 0;
+}
+
+.dropleft .dropdown-menu {
+  top: 0;
+  right: 100%;
+  left: auto;
+  margin-top: 0;
+  margin-right: 0.125rem;
+}
+
+.dropleft .dropdown-toggle::after {
+  display: inline-block;
+  margin-left: 0.255em;
+  vertical-align: 0.255em;
+  content: "";
+}
+
+.dropleft .dropdown-toggle::after {
+  display: none;
+}
+
+.dropleft .dropdown-toggle::before {
+  display: inline-block;
+  margin-right: 0.255em;
+  vertical-align: 0.255em;
+  content: "";
+  border-top: 0.3em solid transparent;
+  border-right: 0.3em solid;
+  border-bottom: 0.3em solid transparent;
+}
+
+.dropleft .dropdown-toggle:empty::after {
+  margin-left: 0;
+}
+
+.dropleft .dropdown-toggle::before {
+  vertical-align: 0;
+}
+
+.dropdown-menu[x-placement^="top"], .dropdown-menu[x-placement^="right"], .dropdown-menu[x-placement^="bottom"], .dropdown-menu[x-placement^="left"] {
+  right: auto;
+  bottom: auto;
+}
+
+.dropdown-divider {
+  height: 0;
+  margin: 0.5rem 0;
+  overflow: hidden;
+  border-top: 1px solid #e9ecef;
+}
+
+.dropdown-item {
+  display: block;
+  width: 100%;
+  padding: 0.25rem 1.5rem;
+  clear: both;
+  font-weight: 400;
+  color: #212529;
+  text-align: inherit;
+  white-space: nowrap;
+  background-color: transparent;
+  border: 0;
+}
+
+.dropdown-item:hover, .dropdown-item:focus {
+  color: #16181b;
+  text-decoration: none;
+  background-color: #e9ecef;
+}
+
+.dropdown-item.active, .dropdown-item:active {
+  color: #fff;
+  text-decoration: none;
+  background-color: #007bff;
+}
+
+.dropdown-item.disabled, .dropdown-item:disabled {
+  color: #adb5bd;
+  pointer-events: none;
+  background-color: transparent;
+}
+
+.dropdown-menu.show {
+  display: block;
+}
+
+.dropdown-header {
+  display: block;
+  padding: 0.5rem 1.5rem;
+  margin-bottom: 0;
+  font-size: 0.875rem;
+  color: #6c757d;
+  white-space: nowrap;
+}
+
+.dropdown-item-text {
+  display: block;
+  padding: 0.25rem 1.5rem;
+  color: #212529;
+}
+
+.btn-group,
+.btn-group-vertical {
+  position: relative;
+  display: -webkit-inline-box;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  vertical-align: middle;
+}
+
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+  position: relative;
+  -webkit-box-flex: 1;
+      -ms-flex: 1 1 auto;
+          flex: 1 1 auto;
+}
+
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover {
+  z-index: 1;
+}
+
+.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,
+.btn-group-vertical > .btn:focus,
+.btn-group-vertical > .btn:active,
+.btn-group-vertical > .btn.active {
+  z-index: 1;
+}
+
+.btn-toolbar {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  -webkit-box-pack: start;
+      -ms-flex-pack: start;
+          justify-content: flex-start;
+}
+
+.btn-toolbar .input-group {
+  width: auto;
+}
+
+.btn-group > .btn:not(:first-child),
+.btn-group > .btn-group:not(:first-child) {
+  margin-left: -1px;
+}
+
+.btn-group > .btn:not(:last-child):not(.dropdown-toggle),
+.btn-group > .btn-group:not(:last-child) > .btn {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn:not(:first-child),
+.btn-group > .btn-group:not(:first-child) > .btn {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.dropdown-toggle-split {
+  padding-right: 0.5625rem;
+  padding-left: 0.5625rem;
+}
+
+.dropdown-toggle-split::after,
+.dropup .dropdown-toggle-split::after,
+.dropright .dropdown-toggle-split::after {
+  margin-left: 0;
+}
+
+.dropleft .dropdown-toggle-split::before {
+  margin-right: 0;
+}
+
+.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {
+  padding-right: 0.375rem;
+  padding-left: 0.375rem;
+}
+
+.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {
+  padding-right: 0.75rem;
+  padding-left: 0.75rem;
+}
+
+.btn-group-vertical {
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: column;
+          flex-direction: column;
+  -webkit-box-align: start;
+      -ms-flex-align: start;
+          align-items: flex-start;
+  -webkit-box-pack: center;
+      -ms-flex-pack: center;
+          justify-content: center;
+}
+
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group {
+  width: 100%;
+}
+
+.btn-group-vertical > .btn:not(:first-child),
+.btn-group-vertical > .btn-group:not(:first-child) {
+  margin-top: -1px;
+}
+
+.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),
+.btn-group-vertical > .btn-group:not(:last-child) > .btn {
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical > .btn:not(:first-child),
+.btn-group-vertical > .btn-group:not(:first-child) > .btn {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+
+.btn-group-toggle > .btn,
+.btn-group-toggle > .btn-group > .btn {
+  margin-bottom: 0;
+}
+
+.btn-group-toggle > .btn input[type="radio"],
+.btn-group-toggle > .btn input[type="checkbox"],
+.btn-group-toggle > .btn-group > .btn input[type="radio"],
+.btn-group-toggle > .btn-group > .btn input[type="checkbox"] {
+  position: absolute;
+  clip: rect(0, 0, 0, 0);
+  pointer-events: none;
+}
+
+.input-group {
+  position: relative;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  -webkit-box-align: stretch;
+      -ms-flex-align: stretch;
+          align-items: stretch;
+  width: 100%;
+  background: #000;
+}
+
+.input-group > .form-control,
+.input-group > .form-control-plaintext,
+.input-group > .custom-select,
+.input-group > .custom-file {
+  position: relative;
+  -webkit-box-flex: 1;
+      -ms-flex: 1 1 auto;
+          flex: 1 1 auto;
+  width: 1%;
+  min-width: 0;
+  margin-bottom: 0;
+}
+
+.input-group > .form-control + .form-control,
+.input-group > .form-control + .custom-select,
+.input-group > .form-control + .custom-file,
+.input-group > .form-control-plaintext + .form-control,
+.input-group > .form-control-plaintext + .custom-select,
+.input-group > .form-control-plaintext + .custom-file,
+.input-group > .custom-select + .form-control,
+.input-group > .custom-select + .custom-select,
+.input-group > .custom-select + .custom-file,
+.input-group > .custom-file + .form-control,
+.input-group > .custom-file + .custom-select,
+.input-group > .custom-file + .custom-file {
+  margin-left: -1px;
+}
+
+.input-group > .form-control:focus,
+.input-group > .custom-select:focus,
+.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label {
+  z-index: 3;
+}
+
+.input-group > .custom-file .custom-file-input:focus {
+  z-index: 4;
+}
+
+.input-group > .form-control:not(:first-child),
+.input-group > .custom-select:not(:first-child) {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.input-group > .custom-file {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+}
+
+.input-group > .custom-file:not(:last-child) .custom-file-label,
+.input-group > .custom-file:not(:first-child) .custom-file-label {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.input-group:not(.has-validation) > .form-control:not(:last-child),
+.input-group:not(.has-validation) > .custom-select:not(:last-child),
+.input-group:not(.has-validation) > .custom-file:not(:last-child) .custom-file-label::after {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.input-group.has-validation > .form-control:nth-last-child(n + 3),
+.input-group.has-validation > .custom-select:nth-last-child(n + 3),
+.input-group.has-validation > .custom-file:nth-last-child(n + 3) .custom-file-label::after {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.input-group-prepend,
+.input-group-append {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+}
+
+.input-group-prepend .btn,
+.input-group-append .btn {
+  position: relative;
+  z-index: 2;
+}
+
+.input-group-prepend .btn:focus,
+.input-group-append .btn:focus {
+  z-index: 3;
+}
+
+.input-group-prepend .btn + .btn,
+.input-group-prepend .btn + .input-group-text,
+.input-group-prepend .input-group-text + .input-group-text,
+.input-group-prepend .input-group-text + .btn,
+.input-group-append .btn + .btn,
+.input-group-append .btn + .input-group-text,
+.input-group-append .input-group-text + .input-group-text,
+.input-group-append .input-group-text + .btn {
+  margin-left: -1px;
+}
+
+.input-group-prepend {
+  margin-right: -1px;
+}
+
+.input-group-append {
+  margin-left: -1px;
+}
+
+.input-group-text {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+  padding: 0.375rem 0.75rem;
+  margin-bottom: 0;
+  font-size: 1rem;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #495057;
+  text-align: center;
+  white-space: nowrap;
+  background-color: #e9ecef;
+  border: 1px solid #ced4da;
+  border-radius: 0.25rem;
+}
+
+.input-group-text input[type="radio"],
+.input-group-text input[type="checkbox"] {
+  margin-top: 0;
+}
+
+.input-group-lg > .form-control:not(textarea),
+.input-group-lg > .custom-select {
+  height: calc(1.5em + 1rem + 2px);
+}
+
+.input-group-lg > .form-control,
+.input-group-lg > .custom-select,
+.input-group-lg > .input-group-prepend > .input-group-text,
+.input-group-lg > .input-group-append > .input-group-text,
+.input-group-lg > .input-group-prepend > .btn,
+.input-group-lg > .input-group-append > .btn {
+  padding: 0.5rem 1rem;
+  font-size: 1.25rem;
+  line-height: 1.5;
+  border-radius: 0.3rem;
+}
+
+.input-group-sm > .form-control:not(textarea),
+.input-group-sm > .custom-select {
+  height: calc(1.5em + 0.5rem + 2px);
+}
+
+.input-group-sm > .form-control,
+.input-group-sm > .custom-select,
+.input-group-sm > .input-group-prepend > .input-group-text,
+.input-group-sm > .input-group-append > .input-group-text,
+.input-group-sm > .input-group-prepend > .btn,
+.input-group-sm > .input-group-append > .btn {
+  padding: 0.25rem 0.5rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+  border-radius: 0.2rem;
+}
+
+.input-group-lg > .custom-select,
+.input-group-sm > .custom-select {
+  padding-right: 1.75rem;
+}
+
+.input-group > .input-group-prepend > .btn,
+.input-group > .input-group-prepend > .input-group-text,
+.input-group:not(.has-validation) > .input-group-append:not(:last-child) > .btn,
+.input-group:not(.has-validation) > .input-group-append:not(:last-child) > .input-group-text,
+.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .btn,
+.input-group.has-validation > .input-group-append:nth-last-child(n + 3) > .input-group-text,
+.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),
+.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {
+  border-top-right-radius: 0;
+  border-bottom-right-radius: 0;
+}
+
+.input-group > .input-group-append > .btn,
+.input-group > .input-group-append > .input-group-text,
+.input-group > .input-group-prepend:not(:first-child) > .btn,
+.input-group > .input-group-prepend:not(:first-child) > .input-group-text,
+.input-group > .input-group-prepend:first-child > .btn:not(:first-child),
+.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {
+  border-top-left-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.custom-control {
+  position: relative;
+  z-index: 1;
+  display: block;
+  min-height: 1.5rem;
+  padding-left: 1.5rem;
+  color-adjust: exact;
+}
+
+.custom-control-inline {
+  display: -webkit-inline-box;
+  display: -ms-inline-flexbox;
+  display: inline-flex;
+  margin-right: 1rem;
+}
+
+.custom-control-input {
+  position: absolute;
+  left: 0;
+  z-index: -1;
+  width: 1rem;
+  height: 1.25rem;
+  opacity: 0;
+}
+
+.custom-control-input:checked ~ .custom-control-label::before {
+  color: #fff;
+  border-color: #007bff;
+  background-color: #007bff;
+}
+
+.custom-control-input:focus ~ .custom-control-label::before {
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {
+  border-color: #80bdff;
+}
+
+.custom-control-input:not(:disabled):active ~ .custom-control-label::before {
+  color: #fff;
+  background-color: #b3d7ff;
+  border-color: #b3d7ff;
+}
+
+.custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label {
+  color: #6c757d;
+}
+
+.custom-control-input[disabled] ~ .custom-control-label::before, .custom-control-input:disabled ~ .custom-control-label::before {
+  background-color: #e9ecef;
+}
+
+.custom-control-label {
+  position: relative;
+  margin-bottom: 0;
+  vertical-align: top;
+}
+
+.custom-control-label::before {
+  position: absolute;
+  top: 0.25rem;
+  left: -1.5rem;
+  display: block;
+  width: 1rem;
+  height: 1rem;
+  pointer-events: none;
+  content: "";
+  background-color: #fff;
+  border: #adb5bd solid 1px;
+}
+
+.custom-control-label::after {
+  position: absolute;
+  top: 0.25rem;
+  left: -1.5rem;
+  display: block;
+  width: 1rem;
+  height: 1rem;
+  content: "";
+  background: 50% / 50% 50% no-repeat;
+}
+
+.custom-checkbox .custom-control-label::before {
+  border-radius: 0.25rem;
+}
+
+.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e");
+}
+
+.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {
+  border-color: #007bff;
+  background-color: #007bff;
+}
+
+.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e");
+}
+
+.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {
+  background-color: rgba(0, 123, 255, 0.5);
+}
+
+.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {
+  background-color: rgba(0, 123, 255, 0.5);
+}
+
+.custom-radio .custom-control-label::before {
+  border-radius: 50%;
+}
+
+.custom-radio .custom-control-input:checked ~ .custom-control-label::after {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e");
+}
+
+.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {
+  background-color: rgba(0, 123, 255, 0.5);
+}
+
+.custom-switch {
+  padding-left: 2.25rem;
+}
+
+.custom-switch .custom-control-label::before {
+  left: -2.25rem;
+  width: 1.75rem;
+  pointer-events: all;
+  border-radius: 0.5rem;
+}
+
+.custom-switch .custom-control-label::after {
+  top: calc(0.25rem + 2px);
+  left: calc(-2.25rem + 2px);
+  width: calc(1rem - 4px);
+  height: calc(1rem - 4px);
+  background-color: #adb5bd;
+  border-radius: 0.5rem;
+  -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .custom-switch .custom-control-label::after {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.custom-switch .custom-control-input:checked ~ .custom-control-label::after {
+  background-color: #fff;
+  -webkit-transform: translateX(0.75rem);
+          transform: translateX(0.75rem);
+}
+
+.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {
+  background-color: rgba(0, 123, 255, 0.5);
+}
+
+.custom-select {
+  display: inline-block;
+  width: 100%;
+  height: calc(1.5em + 0.75rem + 2px);
+  padding: 0.375rem 1.75rem 0.375rem 0.75rem;
+  font-size: 1rem;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #495057;
+  vertical-align: middle;
+  background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") right 0.75rem center/8px 10px no-repeat;
+  border: 1px solid #ced4da;
+  border-radius: 0.25rem;
+  -webkit-appearance: none;
+     -moz-appearance: none;
+          appearance: none;
+}
+
+.custom-select:focus {
+  border-color: #80bdff;
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.custom-select:focus::-ms-value {
+  color: #495057;
+  background-color: #fff;
+}
+
+.custom-select[multiple], .custom-select[size]:not([size="1"]) {
+  height: auto;
+  padding-right: 0.75rem;
+  background-image: none;
+}
+
+.custom-select:disabled {
+  color: #6c757d;
+  background-color: #e9ecef;
+}
+
+.custom-select::-ms-expand {
+  display: none;
+}
+
+.custom-select:-moz-focusring {
+  color: transparent;
+  text-shadow: 0 0 0 #495057;
+}
+
+.custom-select-sm {
+  height: calc(1.5em + 0.5rem + 2px);
+  padding-top: 0.25rem;
+  padding-bottom: 0.25rem;
+  padding-left: 0.5rem;
+  font-size: 0.875rem;
+}
+
+.custom-select-lg {
+  height: calc(1.5em + 1rem + 2px);
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+  padding-left: 1rem;
+  font-size: 1.25rem;
+}
+
+.custom-file {
+  position: relative;
+  display: inline-block;
+  width: 100%;
+  height: calc(1.5em + 0.75rem + 2px);
+  margin-bottom: 0;
+}
+
+.custom-file-input {
+  position: relative;
+  z-index: 2;
+  width: 100%;
+  height: calc(1.5em + 0.75rem + 2px);
+  margin: 0;
+  overflow: hidden;
+  opacity: 0;
+}
+
+.custom-file-input:focus ~ .custom-file-label {
+  border-color: #80bdff;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.custom-file-input[disabled] ~ .custom-file-label,
+.custom-file-input:disabled ~ .custom-file-label {
+  background-color: #e9ecef;
+}
+
+.custom-file-input:lang(en) ~ .custom-file-label::after {
+  content: "Browse";
+}
+
+.custom-file-input ~ .custom-file-label[data-browse]::after {
+  content: attr(data-browse);
+}
+
+.custom-file-label {
+  position: absolute;
+  top: 0;
+  right: 0;
+  left: 0;
+  z-index: 1;
+  height: calc(1.5em + 0.75rem + 2px);
+  padding: 0.375rem 0.75rem;
+  overflow: hidden;
+  font-weight: 400;
+  line-height: 1.5;
+  color: #495057;
+  background-color: #fff;
+  border: 1px solid #ced4da;
+  border-radius: 0.25rem;
+}
+
+.custom-file-label::after {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 3;
+  display: block;
+  height: calc(1.5em + 0.75rem);
+  padding: 0.375rem 0.75rem;
+  line-height: 1.5;
+  color: #495057;
+  content: "Browse";
+  background-color: #e9ecef;
+  border-left: inherit;
+  border-radius: 0 0.25rem 0.25rem 0;
+}
+
+.custom-range {
+  width: 100%;
+  height: 1.4rem;
+  padding: 0;
+  background-color: transparent;
+  -webkit-appearance: none;
+     -moz-appearance: none;
+          appearance: none;
+}
+
+.custom-range:focus {
+  outline: 0;
+}
+
+.custom-range:focus::-webkit-slider-thumb {
+  -webkit-box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+          box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.custom-range:focus::-moz-range-thumb {
+  box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.custom-range:focus::-ms-thumb {
+  box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.custom-range::-moz-focus-outer {
+  border: 0;
+}
+
+.custom-range::-webkit-slider-thumb {
+  width: 1rem;
+  height: 1rem;
+  margin-top: -0.25rem;
+  background-color: #007bff;
+  border: 0;
+  border-radius: 1rem;
+  -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  -webkit-appearance: none;
+          appearance: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .custom-range::-webkit-slider-thumb {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.custom-range::-webkit-slider-thumb:active {
+  background-color: #b3d7ff;
+}
+
+.custom-range::-webkit-slider-runnable-track {
+  width: 100%;
+  height: 0.5rem;
+  color: transparent;
+  cursor: pointer;
+  background-color: #dee2e6;
+  border-color: transparent;
+  border-radius: 1rem;
+}
+
+.custom-range::-moz-range-thumb {
+  width: 1rem;
+  height: 1rem;
+  background-color: #007bff;
+  border: 0;
+  border-radius: 1rem;
+  -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  -moz-appearance: none;
+       appearance: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .custom-range::-moz-range-thumb {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.custom-range::-moz-range-thumb:active {
+  background-color: #b3d7ff;
+}
+
+.custom-range::-moz-range-track {
+  width: 100%;
+  height: 0.5rem;
+  color: transparent;
+  cursor: pointer;
+  background-color: #dee2e6;
+  border-color: transparent;
+  border-radius: 1rem;
+}
+
+.custom-range::-ms-thumb {
+  width: 1rem;
+  height: 1rem;
+  margin-top: 0;
+  margin-right: 0.2rem;
+  margin-left: 0.2rem;
+  background-color: #007bff;
+  border: 0;
+  border-radius: 1rem;
+  -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  appearance: none;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .custom-range::-ms-thumb {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.custom-range::-ms-thumb:active {
+  background-color: #b3d7ff;
+}
+
+.custom-range::-ms-track {
+  width: 100%;
+  height: 0.5rem;
+  color: transparent;
+  cursor: pointer;
+  background-color: transparent;
+  border-color: transparent;
+  border-width: 0.5rem;
+}
+
+.custom-range::-ms-fill-lower {
+  background-color: #dee2e6;
+  border-radius: 1rem;
+}
+
+.custom-range::-ms-fill-upper {
+  margin-right: 15px;
+  background-color: #dee2e6;
+  border-radius: 1rem;
+}
+
+.custom-range:disabled::-webkit-slider-thumb {
+  background-color: #adb5bd;
+}
+
+.custom-range:disabled::-webkit-slider-runnable-track {
+  cursor: default;
+}
+
+.custom-range:disabled::-moz-range-thumb {
+  background-color: #adb5bd;
+}
+
+.custom-range:disabled::-moz-range-track {
+  cursor: default;
+}
+
+.custom-range:disabled::-ms-thumb {
+  background-color: #adb5bd;
+}
+
+.custom-control-label::before,
+.custom-file-label,
+.custom-select {
+  -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .custom-control-label::before,
+  .custom-file-label,
+  .custom-select {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.nav {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+
+.nav-link {
+  display: block;
+  padding: 0.5rem 1rem;
+}
+
+.nav-link:hover, .nav-link:focus {
+  text-decoration: none;
+}
+
+.nav-link.disabled {
+  color: #6c757d;
+  pointer-events: none;
+  cursor: default;
+}
+
+.nav-tabs {
+  border-bottom: 1px solid #dee2e6;
+}
+
+.nav-tabs .nav-link {
+  margin-bottom: -1px;
+  border: 1px solid transparent;
+  border-top-left-radius: 0.25rem;
+  border-top-right-radius: 0.25rem;
+}
+
+.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {
+  border-color: #e9ecef #e9ecef #dee2e6;
+}
+
+.nav-tabs .nav-link.disabled {
+  color: #6c757d;
+  background-color: transparent;
+  border-color: transparent;
+}
+
+.nav-tabs .nav-link.active,
+.nav-tabs .nav-item.show .nav-link {
+  color: #495057;
+  background-color: #fff;
+  border-color: #dee2e6 #dee2e6 #fff;
+}
+
+.nav-tabs .dropdown-menu {
+  margin-top: -1px;
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+
+.nav-pills .nav-link {
+  border-radius: 0.25rem;
+}
+
+.nav-pills .nav-link.active,
+.nav-pills .show > .nav-link {
+  color: #fff;
+  background-color: #007bff;
+}
+
+.nav-fill > .nav-link,
+.nav-fill .nav-item {
+  -webkit-box-flex: 1;
+      -ms-flex: 1 1 auto;
+          flex: 1 1 auto;
+  text-align: center;
+}
+
+.nav-justified > .nav-link,
+.nav-justified .nav-item {
+  -ms-flex-preferred-size: 0;
+      flex-basis: 0;
+  -webkit-box-flex: 1;
+      -ms-flex-positive: 1;
+          flex-grow: 1;
+  text-align: center;
+}
+
+.tab-content > .tab-pane {
+  display: none;
+}
+
+.tab-content > .active {
+  display: block;
+}
+
+.navbar {
+  position: relative;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+  -webkit-box-pack: justify;
+      -ms-flex-pack: justify;
+          justify-content: space-between;
+  padding: 0.5rem 1rem;
+}
+
+.navbar .container,
+.navbar .container-fluid, .navbar .container-sm, .navbar .container-md, .navbar .container-lg, .navbar .container-xl {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+  -webkit-box-pack: justify;
+      -ms-flex-pack: justify;
+          justify-content: space-between;
+}
+
+.navbar-brand {
+  display: inline-block;
+  padding-top: 0.3125rem;
+  padding-bottom: 0.3125rem;
+  margin-right: 1rem;
+  font-size: 1.25rem;
+  line-height: inherit;
+  white-space: nowrap;
+}
+
+.navbar-brand:hover, .navbar-brand:focus {
+  text-decoration: none;
+}
+
+.navbar-nav {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: column;
+          flex-direction: column;
+  padding-left: 0;
+  margin-bottom: 0;
+  list-style: none;
+}
+
+.navbar-nav .nav-link {
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.navbar-nav .dropdown-menu {
+  position: static;
+  float: none;
+}
+
+.navbar-text {
+  display: inline-block;
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+}
+
+.navbar-collapse {
+  -ms-flex-preferred-size: 100%;
+      flex-basis: 100%;
+  -webkit-box-flex: 1;
+      -ms-flex-positive: 1;
+          flex-grow: 1;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+}
+
+.navbar-toggler {
+  padding: 0.25rem 0.75rem;
+  font-size: 1.25rem;
+  line-height: 1;
+  background-color: transparent;
+  border: 1px solid transparent;
+  border-radius: 0.25rem;
+}
+
+.navbar-toggler:hover, .navbar-toggler:focus {
+  text-decoration: none;
+}
+
+.navbar-toggler-icon {
+  display: inline-block;
+  width: 1.5em;
+  height: 1.5em;
+  vertical-align: middle;
+  content: "";
+  background: 50% / 100% 100% no-repeat;
+}
+
+.navbar-nav-scroll {
+  max-height: 75vh;
+  overflow-y: auto;
+}
+
+@media (max-width: 575.98px) {
+  .navbar-expand-sm > .container,
+  .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+
+@media (min-width: 576px) {
+  .navbar-expand-sm {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-flow: row nowrap;
+            flex-flow: row nowrap;
+    -webkit-box-pack: start;
+        -ms-flex-pack: start;
+            justify-content: flex-start;
+  }
+  .navbar-expand-sm .navbar-nav {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-direction: row;
+            flex-direction: row;
+  }
+  .navbar-expand-sm .navbar-nav .dropdown-menu {
+    position: absolute;
+  }
+  .navbar-expand-sm .navbar-nav .nav-link {
+    padding-right: 0.5rem;
+    padding-left: 0.5rem;
+  }
+  .navbar-expand-sm > .container,
+  .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl {
+    -ms-flex-wrap: nowrap;
+        flex-wrap: nowrap;
+  }
+  .navbar-expand-sm .navbar-nav-scroll {
+    overflow: visible;
+  }
+  .navbar-expand-sm .navbar-collapse {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+    -ms-flex-preferred-size: auto;
+        flex-basis: auto;
+  }
+  .navbar-expand-sm .navbar-toggler {
+    display: none;
+  }
+}
+
+@media (max-width: 767.98px) {
+  .navbar-expand-md > .container,
+  .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+
+@media (min-width: 768px) {
+  .navbar-expand-md {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-flow: row nowrap;
+            flex-flow: row nowrap;
+    -webkit-box-pack: start;
+        -ms-flex-pack: start;
+            justify-content: flex-start;
+  }
+  .navbar-expand-md .navbar-nav {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-direction: row;
+            flex-direction: row;
+  }
+  .navbar-expand-md .navbar-nav .dropdown-menu {
+    position: absolute;
+  }
+  .navbar-expand-md .navbar-nav .nav-link {
+    padding-right: 0.5rem;
+    padding-left: 0.5rem;
+  }
+  .navbar-expand-md > .container,
+  .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl {
+    -ms-flex-wrap: nowrap;
+        flex-wrap: nowrap;
+  }
+  .navbar-expand-md .navbar-nav-scroll {
+    overflow: visible;
+  }
+  .navbar-expand-md .navbar-collapse {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+    -ms-flex-preferred-size: auto;
+        flex-basis: auto;
+  }
+  .navbar-expand-md .navbar-toggler {
+    display: none;
+  }
+}
+
+@media (max-width: 991.98px) {
+  .navbar-expand-lg > .container,
+  .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+
+@media (min-width: 992px) {
+  .navbar-expand-lg {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-flow: row nowrap;
+            flex-flow: row nowrap;
+    -webkit-box-pack: start;
+        -ms-flex-pack: start;
+            justify-content: flex-start;
+  }
+  .navbar-expand-lg .navbar-nav {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-direction: row;
+            flex-direction: row;
+  }
+  .navbar-expand-lg .navbar-nav .dropdown-menu {
+    position: absolute;
+  }
+  .navbar-expand-lg .navbar-nav .nav-link {
+    padding-right: 0.5rem;
+    padding-left: 0.5rem;
+  }
+  .navbar-expand-lg > .container,
+  .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl {
+    -ms-flex-wrap: nowrap;
+        flex-wrap: nowrap;
+  }
+  .navbar-expand-lg .navbar-nav-scroll {
+    overflow: visible;
+  }
+  .navbar-expand-lg .navbar-collapse {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+    -ms-flex-preferred-size: auto;
+        flex-basis: auto;
+  }
+  .navbar-expand-lg .navbar-toggler {
+    display: none;
+  }
+}
+
+@media (max-width: 1199.98px) {
+  .navbar-expand-xl > .container,
+  .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl {
+    padding-right: 0;
+    padding-left: 0;
+  }
+}
+
+@media (min-width: 1200px) {
+  .navbar-expand-xl {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-flow: row nowrap;
+            flex-flow: row nowrap;
+    -webkit-box-pack: start;
+        -ms-flex-pack: start;
+            justify-content: flex-start;
+  }
+  .navbar-expand-xl .navbar-nav {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-direction: row;
+            flex-direction: row;
+  }
+  .navbar-expand-xl .navbar-nav .dropdown-menu {
+    position: absolute;
+  }
+  .navbar-expand-xl .navbar-nav .nav-link {
+    padding-right: 0.5rem;
+    padding-left: 0.5rem;
+  }
+  .navbar-expand-xl > .container,
+  .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl {
+    -ms-flex-wrap: nowrap;
+        flex-wrap: nowrap;
+  }
+  .navbar-expand-xl .navbar-nav-scroll {
+    overflow: visible;
+  }
+  .navbar-expand-xl .navbar-collapse {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+    -ms-flex-preferred-size: auto;
+        flex-basis: auto;
+  }
+  .navbar-expand-xl .navbar-toggler {
+    display: none;
+  }
+}
+
+.navbar-expand {
+  -webkit-box-orient: horizontal;
+  -webkit-box-direction: normal;
+      -ms-flex-flow: row nowrap;
+          flex-flow: row nowrap;
+  -webkit-box-pack: start;
+      -ms-flex-pack: start;
+          justify-content: flex-start;
+}
+
+.navbar-expand > .container,
+.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl {
+  padding-right: 0;
+  padding-left: 0;
+}
+
+.navbar-expand .navbar-nav {
+  -webkit-box-orient: horizontal;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: row;
+          flex-direction: row;
+}
+
+.navbar-expand .navbar-nav .dropdown-menu {
+  position: absolute;
+}
+
+.navbar-expand .navbar-nav .nav-link {
+  padding-right: 0.5rem;
+  padding-left: 0.5rem;
+}
+
+.navbar-expand > .container,
+.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl {
+  -ms-flex-wrap: nowrap;
+      flex-wrap: nowrap;
+}
+
+.navbar-expand .navbar-nav-scroll {
+  overflow: visible;
+}
+
+.navbar-expand .navbar-collapse {
+  display: -webkit-box !important;
+  display: -ms-flexbox !important;
+  display: flex !important;
+  -ms-flex-preferred-size: auto;
+      flex-basis: auto;
+}
+
+.navbar-expand .navbar-toggler {
+  display: none;
+}
+
+.navbar-light .navbar-brand {
+  color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {
+  color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-light .navbar-nav .nav-link {
+  color: rgba(0, 0, 0, 0.5);
+}
+
+.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {
+  color: rgba(0, 0, 0, 0.7);
+}
+
+.navbar-light .navbar-nav .nav-link.disabled {
+  color: rgba(0, 0, 0, 0.3);
+}
+
+.navbar-light .navbar-nav .show > .nav-link,
+.navbar-light .navbar-nav .active > .nav-link,
+.navbar-light .navbar-nav .nav-link.show,
+.navbar-light .navbar-nav .nav-link.active {
+  color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-light .navbar-toggler {
+  color: rgba(0, 0, 0, 0.5);
+  border-color: rgba(0, 0, 0, 0.1);
+}
+
+.navbar-light .navbar-toggler-icon {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
+}
+
+.navbar-light .navbar-text {
+  color: rgba(0, 0, 0, 0.5);
+}
+
+.navbar-light .navbar-text a {
+  color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {
+  color: rgba(0, 0, 0, 0.9);
+}
+
+.navbar-dark .navbar-brand {
+  color: #fff;
+}
+
+.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {
+  color: #fff;
+}
+
+.navbar-dark .navbar-nav .nav-link {
+  color: rgba(255, 255, 255, 0.5);
+}
+
+.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {
+  color: rgba(255, 255, 255, 0.75);
+}
+
+.navbar-dark .navbar-nav .nav-link.disabled {
+  color: rgba(255, 255, 255, 0.25);
+}
+
+.navbar-dark .navbar-nav .show > .nav-link,
+.navbar-dark .navbar-nav .active > .nav-link,
+.navbar-dark .navbar-nav .nav-link.show,
+.navbar-dark .navbar-nav .nav-link.active {
+  color: #fff;
+}
+
+.navbar-dark .navbar-toggler {
+  color: rgba(255, 255, 255, 0.5);
+  border-color: rgba(255, 255, 255, 0.1);
+}
+
+.navbar-dark .navbar-toggler-icon {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.5%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
+}
+
+.navbar-dark .navbar-text {
+  color: rgba(255, 255, 255, 0.5);
+}
+
+.navbar-dark .navbar-text a {
+  color: #fff;
+}
+
+.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {
+  color: #fff;
+}
+
+.card {
+  position: relative;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: column;
+          flex-direction: column;
+  min-width: 0;
+  word-wrap: break-word;
+  background-color: #fff;
+  background-clip: border-box;
+  border: 1px solid rgba(0, 0, 0, 0.125);
+  border-radius: 0.25rem;
+}
+
+.card > hr {
+  margin-right: 0;
+  margin-left: 0;
+}
+
+.card > .list-group {
+  border-top: inherit;
+  border-bottom: inherit;
+}
+
+.card > .list-group:first-child {
+  border-top-width: 0;
+  border-top-left-radius: calc(0.25rem - 1px);
+  border-top-right-radius: calc(0.25rem - 1px);
+}
+
+.card > .list-group:last-child {
+  border-bottom-width: 0;
+  border-bottom-right-radius: calc(0.25rem - 1px);
+  border-bottom-left-radius: calc(0.25rem - 1px);
+}
+
+.card > .card-header + .list-group,
+.card > .list-group + .card-footer {
+  border-top: 0;
+}
+
+.card-body {
+  -webkit-box-flex: 1;
+      -ms-flex: 1 1 auto;
+          flex: 1 1 auto;
+  min-height: 1px;
+  padding: 1.25rem;
+}
+
+.card-title {
+  margin-bottom: 0.75rem;
+}
+
+.card-subtitle {
+  margin-top: -0.375rem;
+  margin-bottom: 0;
+}
+
+.card-text:last-child {
+  margin-bottom: 0;
+}
+
+.card-link:hover {
+  text-decoration: none;
+}
+
+.card-link + .card-link {
+  margin-left: 1.25rem;
+}
+
+.card-header {
+  padding: 0.75rem 1.25rem;
+  margin-bottom: 0;
+  background-color: rgba(0, 0, 0, 0.03);
+  border-bottom: 1px solid rgba(0, 0, 0, 0.125);
+}
+
+.card-header:first-child {
+  border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;
+}
+
+.card-footer {
+  padding: 0.75rem 1.25rem;
+  background-color: rgba(0, 0, 0, 0.03);
+  border-top: 1px solid rgba(0, 0, 0, 0.125);
+}
+
+.card-footer:last-child {
+  border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);
+}
+
+.card-header-tabs {
+  margin-right: -0.625rem;
+  margin-bottom: -0.75rem;
+  margin-left: -0.625rem;
+  border-bottom: 0;
+}
+
+.card-header-pills {
+  margin-right: -0.625rem;
+  margin-left: -0.625rem;
+}
+
+.card-img-overlay {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  padding: 1.25rem;
+  border-radius: calc(0.25rem - 1px);
+}
+
+.card-img,
+.card-img-top,
+.card-img-bottom {
+  -ms-flex-negative: 0;
+      flex-shrink: 0;
+  width: 100%;
+}
+
+.card-img,
+.card-img-top {
+  border-top-left-radius: calc(0.25rem - 1px);
+  border-top-right-radius: calc(0.25rem - 1px);
+}
+
+.card-img,
+.card-img-bottom {
+  border-bottom-right-radius: calc(0.25rem - 1px);
+  border-bottom-left-radius: calc(0.25rem - 1px);
+}
+
+.card-deck .card {
+  margin-bottom: 15px;
+}
+
+@media (min-width: 576px) {
+  .card-deck {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-flow: row wrap;
+            flex-flow: row wrap;
+    margin-right: -15px;
+    margin-left: -15px;
+  }
+  .card-deck .card {
+    -webkit-box-flex: 1;
+        -ms-flex: 1 0 0%;
+            flex: 1 0 0%;
+    margin-right: 15px;
+    margin-bottom: 0;
+    margin-left: 15px;
+  }
+}
+
+.card-group > .card {
+  margin-bottom: 15px;
+}
+
+@media (min-width: 576px) {
+  .card-group {
+    display: -webkit-box;
+    display: -ms-flexbox;
+    display: flex;
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-flow: row wrap;
+            flex-flow: row wrap;
+  }
+  .card-group > .card {
+    -webkit-box-flex: 1;
+        -ms-flex: 1 0 0%;
+            flex: 1 0 0%;
+    margin-bottom: 0;
+  }
+  .card-group > .card + .card {
+    margin-left: 0;
+    border-left: 0;
+  }
+  .card-group > .card:not(:last-child) {
+    border-top-right-radius: 0;
+    border-bottom-right-radius: 0;
+  }
+  .card-group > .card:not(:last-child) .card-img-top,
+  .card-group > .card:not(:last-child) .card-header {
+    border-top-right-radius: 0;
+  }
+  .card-group > .card:not(:last-child) .card-img-bottom,
+  .card-group > .card:not(:last-child) .card-footer {
+    border-bottom-right-radius: 0;
+  }
+  .card-group > .card:not(:first-child) {
+    border-top-left-radius: 0;
+    border-bottom-left-radius: 0;
+  }
+  .card-group > .card:not(:first-child) .card-img-top,
+  .card-group > .card:not(:first-child) .card-header {
+    border-top-left-radius: 0;
+  }
+  .card-group > .card:not(:first-child) .card-img-bottom,
+  .card-group > .card:not(:first-child) .card-footer {
+    border-bottom-left-radius: 0;
+  }
+}
+
+.card-columns .card {
+  margin-bottom: 0.75rem;
+}
+
+@media (min-width: 576px) {
+  .card-columns {
+    -webkit-column-count: 3;
+            column-count: 3;
+    -webkit-column-gap: 1.25rem;
+            column-gap: 1.25rem;
+    orphans: 1;
+    widows: 1;
+  }
+  .card-columns .card {
+    display: inline-block;
+    width: 100%;
+  }
+}
+
+.accordion {
+  overflow-anchor: none;
+}
+
+.accordion > .card {
+  overflow: hidden;
+}
+
+.accordion > .card:not(:last-of-type) {
+  border-bottom: 0;
+  border-bottom-right-radius: 0;
+  border-bottom-left-radius: 0;
+}
+
+.accordion > .card:not(:first-of-type) {
+  border-top-left-radius: 0;
+  border-top-right-radius: 0;
+}
+
+.accordion > .card > .card-header {
+  border-radius: 0;
+  margin-bottom: -1px;
+}
+
+.breadcrumb {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  padding: 0.75rem 1rem;
+  margin-bottom: 1rem;
+  list-style: none;
+  background-color: #e9ecef;
+  border-radius: 0.25rem;
+}
+
+.breadcrumb-item + .breadcrumb-item {
+  padding-left: 0.5rem;
+}
+
+.breadcrumb-item + .breadcrumb-item::before {
+  float: left;
+  padding-right: 0.5rem;
+  color: #6c757d;
+  content: "/";
+}
+
+.breadcrumb-item + .breadcrumb-item:hover::before {
+  text-decoration: underline;
+}
+
+.breadcrumb-item + .breadcrumb-item:hover::before {
+  text-decoration: none;
+}
+
+.breadcrumb-item.active {
+  color: #6c757d;
+}
+
+.pagination {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  padding-left: 0;
+  list-style: none;
+  border-radius: 0.25rem;
+}
+
+.page-link {
+  position: relative;
+  display: block;
+  padding: 0.5rem 0.75rem;
+  margin-left: -1px;
+  line-height: 1.25;
+  color: #007bff;
+  background-color: #fff;
+  border: 1px solid #dee2e6;
+}
+
+.page-link:hover {
+  z-index: 2;
+  color: #0056b3;
+  text-decoration: none;
+  background-color: #e9ecef;
+  border-color: #dee2e6;
+}
+
+.page-link:focus {
+  z-index: 3;
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+          box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+}
+
+.page-item:first-child .page-link {
+  margin-left: 0;
+  border-top-left-radius: 0.25rem;
+  border-bottom-left-radius: 0.25rem;
+}
+
+.page-item:last-child .page-link {
+  border-top-right-radius: 0.25rem;
+  border-bottom-right-radius: 0.25rem;
+}
+
+.page-item.active .page-link {
+  z-index: 3;
+  color: #fff;
+  background-color: #007bff;
+  border-color: #007bff;
+}
+
+.page-item.disabled .page-link {
+  color: #6c757d;
+  pointer-events: none;
+  cursor: auto;
+  background-color: #fff;
+  border-color: #dee2e6;
+}
+
+.pagination-lg .page-link {
+  padding: 0.75rem 1.5rem;
+  font-size: 1.25rem;
+  line-height: 1.5;
+}
+
+.pagination-lg .page-item:first-child .page-link {
+  border-top-left-radius: 0.3rem;
+  border-bottom-left-radius: 0.3rem;
+}
+
+.pagination-lg .page-item:last-child .page-link {
+  border-top-right-radius: 0.3rem;
+  border-bottom-right-radius: 0.3rem;
+}
+
+.pagination-sm .page-link {
+  padding: 0.25rem 0.5rem;
+  font-size: 0.875rem;
+  line-height: 1.5;
+}
+
+.pagination-sm .page-item:first-child .page-link {
+  border-top-left-radius: 0.2rem;
+  border-bottom-left-radius: 0.2rem;
+}
+
+.pagination-sm .page-item:last-child .page-link {
+  border-top-right-radius: 0.2rem;
+  border-bottom-right-radius: 0.2rem;
+}
+
+.badge {
+  display: inline-block;
+  padding: 0.25em 0.4em;
+  font-size: 75%;
+  font-weight: 700;
+  line-height: 1;
+  text-align: center;
+  white-space: nowrap;
+  vertical-align: baseline;
+  border-radius: 0.25rem;
+  -webkit-transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
+  transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-box-shadow 0.15s ease-in-out;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .badge {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+a.badge:hover, a.badge:focus {
+  text-decoration: none;
+}
+
+.badge:empty {
+  display: none;
+}
+
+.btn .badge {
+  position: relative;
+  top: -1px;
+}
+
+.badge-pill {
+  padding-right: 0.6em;
+  padding-left: 0.6em;
+  border-radius: 10rem;
+}
+
+.badge-primary {
+  color: #fff;
+  background-color: #007bff;
+}
+
+a.badge-primary:hover, a.badge-primary:focus {
+  color: #fff;
+  background-color: #0062cc;
+}
+
+a.badge-primary:focus, a.badge-primary.focus {
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);
+}
+
+.badge-secondary {
+  color: #fff;
+  background-color: #6c757d;
+}
+
+a.badge-secondary:hover, a.badge-secondary:focus {
+  color: #fff;
+  background-color: #545b62;
+}
+
+a.badge-secondary:focus, a.badge-secondary.focus {
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);
+}
+
+.badge-success {
+  color: #fff;
+  background-color: #28a745;
+}
+
+a.badge-success:hover, a.badge-success:focus {
+  color: #fff;
+  background-color: #1e7e34;
+}
+
+a.badge-success:focus, a.badge-success.focus {
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);
+}
+
+.badge-info {
+  color: #fff;
+  background-color: #17a2b8;
+}
+
+a.badge-info:hover, a.badge-info:focus {
+  color: #fff;
+  background-color: #117a8b;
+}
+
+a.badge-info:focus, a.badge-info.focus {
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);
+}
+
+.badge-warning {
+  color: #212529;
+  background-color: #ffc107;
+}
+
+a.badge-warning:hover, a.badge-warning:focus {
+  color: #212529;
+  background-color: #d39e00;
+}
+
+a.badge-warning:focus, a.badge-warning.focus {
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);
+}
+
+.badge-danger {
+  color: #fff;
+  background-color: #dc3545;
+}
+
+a.badge-danger:hover, a.badge-danger:focus {
+  color: #fff;
+  background-color: #bd2130;
+}
+
+a.badge-danger:focus, a.badge-danger.focus {
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);
+}
+
+.badge-light {
+  color: #212529;
+  background-color: #f8f9fa;
+}
+
+a.badge-light:hover, a.badge-light:focus {
+  color: #212529;
+  background-color: #dae0e5;
+}
+
+a.badge-light:focus, a.badge-light.focus {
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);
+}
+
+.badge-dark {
+  color: #fff;
+  background-color: #343a40;
+}
+
+a.badge-dark:hover, a.badge-dark:focus {
+  color: #fff;
+  background-color: #1d2124;
+}
+
+a.badge-dark:focus, a.badge-dark.focus {
+  outline: 0;
+  -webkit-box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+          box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);
+}
+
+.jumbotron {
+  padding: 2rem 1rem;
+  margin-bottom: 2rem;
+  background-color: #e9ecef;
+  border-radius: 0.3rem;
+}
+
+@media (min-width: 576px) {
+  .jumbotron {
+    padding: 4rem 2rem;
+  }
+}
+
+.jumbotron-fluid {
+  padding-right: 0;
+  padding-left: 0;
+  border-radius: 0;
+}
+
+.alert {
+  position: relative;
+  padding: 0.75rem 1.25rem;
+  margin-bottom: 1rem;
+  border: 1px solid transparent;
+  border-radius: 0.25rem;
+}
+
+.alert-heading {
+  color: inherit;
+}
+
+.alert-link {
+  font-weight: 700;
+}
+
+.alert-dismissible {
+  padding-right: 4rem;
+}
+
+.alert-dismissible .close {
+  position: absolute;
+  top: 0;
+  right: 0;
+  z-index: 2;
+  padding: 0.75rem 1.25rem;
+  color: inherit;
+}
+
+.alert-primary {
+  color: #004085;
+  background-color: #cce5ff;
+  border-color: #b8daff;
+}
+
+.alert-primary hr {
+  border-top-color: #9fcdff;
+}
+
+.alert-primary .alert-link {
+  color: #002752;
+}
+
+.alert-secondary {
+  color: #383d41;
+  background-color: #e2e3e5;
+  border-color: #d6d8db;
+}
+
+.alert-secondary hr {
+  border-top-color: #c8cbcf;
+}
+
+.alert-secondary .alert-link {
+  color: #202326;
+}
+
+.alert-success {
+  color: #155724;
+  background-color: #d4edda;
+  border-color: #c3e6cb;
+}
+
+.alert-success hr {
+  border-top-color: #b1dfbb;
+}
+
+.alert-success .alert-link {
+  color: #0b2e13;
+}
+
+.alert-info {
+  color: #0c5460;
+  background-color: #d1ecf1;
+  border-color: #bee5eb;
+}
+
+.alert-info hr {
+  border-top-color: #abdde5;
+}
+
+.alert-info .alert-link {
+  color: #062c33;
+}
+
+.alert-warning {
+  color: #856404;
+  background-color: #fff3cd;
+  border-color: #ffeeba;
+}
+
+.alert-warning hr {
+  border-top-color: #ffe8a1;
+}
+
+.alert-warning .alert-link {
+  color: #533f03;
+}
+
+.alert-danger {
+  color: #721c24;
+  background-color: #f8d7da;
+  border-color: #f5c6cb;
+}
+
+.alert-danger hr {
+  border-top-color: #f1b0b7;
+}
+
+.alert-danger .alert-link {
+  color: #491217;
+}
+
+.alert-light {
+  color: #818182;
+  background-color: #fefefe;
+  border-color: #fdfdfe;
+}
+
+.alert-light hr {
+  border-top-color: #ececf6;
+}
+
+.alert-light .alert-link {
+  color: #686868;
+}
+
+.alert-dark {
+  color: #1b1e21;
+  background-color: #d6d8d9;
+  border-color: #c6c8ca;
+}
+
+.alert-dark hr {
+  border-top-color: #b9bbbe;
+}
+
+.alert-dark .alert-link {
+  color: #040505;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+  from {
+    background-position: 1rem 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+@keyframes progress-bar-stripes {
+  from {
+    background-position: 1rem 0;
+  }
+  to {
+    background-position: 0 0;
+  }
+}
+
+.progress {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  height: 1rem;
+  overflow: hidden;
+  line-height: 0;
+  font-size: 0.75rem;
+  background-color: #e9ecef;
+  border-radius: 0.25rem;
+}
+
+.progress-bar {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: column;
+          flex-direction: column;
+  -webkit-box-pack: center;
+      -ms-flex-pack: center;
+          justify-content: center;
+  overflow: hidden;
+  color: #fff;
+  text-align: center;
+  white-space: nowrap;
+  background-color: #007bff;
+  -webkit-transition: width 0.6s ease;
+  transition: width 0.6s ease;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .progress-bar {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.progress-bar-striped {
+  background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+  background-size: 1rem 1rem;
+}
+
+.progress-bar-animated {
+  -webkit-animation: 1s linear infinite progress-bar-stripes;
+          animation: 1s linear infinite progress-bar-stripes;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .progress-bar-animated {
+    -webkit-animation: none;
+            animation: none;
+  }
+}
+
+.media {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: start;
+      -ms-flex-align: start;
+          align-items: flex-start;
+}
+
+.media-body {
+  -webkit-box-flex: 1;
+      -ms-flex: 1;
+          flex: 1;
+}
+
+.list-group {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: column;
+          flex-direction: column;
+  padding-left: 0;
+  margin-bottom: 0;
+  border-radius: 0.25rem;
+}
+
+.list-group-item-action {
+  width: 100%;
+  color: #495057;
+  text-align: inherit;
+}
+
+.list-group-item-action:hover, .list-group-item-action:focus {
+  z-index: 1;
+  color: #495057;
+  text-decoration: none;
+  background-color: #f8f9fa;
+}
+
+.list-group-item-action:active {
+  color: #212529;
+  background-color: #e9ecef;
+}
+
+.list-group-item {
+  position: relative;
+  display: block;
+  padding: 0.75rem 1.25rem;
+  background-color: #fff;
+  border: 1px solid rgba(0, 0, 0, 0.125);
+}
+
+.list-group-item:first-child {
+  border-top-left-radius: inherit;
+  border-top-right-radius: inherit;
+}
+
+.list-group-item:last-child {
+  border-bottom-right-radius: inherit;
+  border-bottom-left-radius: inherit;
+}
+
+.list-group-item.disabled, .list-group-item:disabled {
+  color: #6c757d;
+  pointer-events: none;
+  background-color: #fff;
+}
+
+.list-group-item.active {
+  z-index: 2;
+  color: #fff;
+  background-color: #007bff;
+  border-color: #007bff;
+}
+
+.list-group-item + .list-group-item {
+  border-top-width: 0;
+}
+
+.list-group-item + .list-group-item.active {
+  margin-top: -1px;
+  border-top-width: 1px;
+}
+
+.list-group-horizontal {
+  -webkit-box-orient: horizontal;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: row;
+          flex-direction: row;
+}
+
+.list-group-horizontal > .list-group-item:first-child {
+  border-bottom-left-radius: 0.25rem;
+  border-top-right-radius: 0;
+}
+
+.list-group-horizontal > .list-group-item:last-child {
+  border-top-right-radius: 0.25rem;
+  border-bottom-left-radius: 0;
+}
+
+.list-group-horizontal > .list-group-item.active {
+  margin-top: 0;
+}
+
+.list-group-horizontal > .list-group-item + .list-group-item {
+  border-top-width: 1px;
+  border-left-width: 0;
+}
+
+.list-group-horizontal > .list-group-item + .list-group-item.active {
+  margin-left: -1px;
+  border-left-width: 1px;
+}
+
+@media (min-width: 576px) {
+  .list-group-horizontal-sm {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-direction: row;
+            flex-direction: row;
+  }
+  .list-group-horizontal-sm > .list-group-item:first-child {
+    border-bottom-left-radius: 0.25rem;
+    border-top-right-radius: 0;
+  }
+  .list-group-horizontal-sm > .list-group-item:last-child {
+    border-top-right-radius: 0.25rem;
+    border-bottom-left-radius: 0;
+  }
+  .list-group-horizontal-sm > .list-group-item.active {
+    margin-top: 0;
+  }
+  .list-group-horizontal-sm > .list-group-item + .list-group-item {
+    border-top-width: 1px;
+    border-left-width: 0;
+  }
+  .list-group-horizontal-sm > .list-group-item + .list-group-item.active {
+    margin-left: -1px;
+    border-left-width: 1px;
+  }
+}
+
+@media (min-width: 768px) {
+  .list-group-horizontal-md {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-direction: row;
+            flex-direction: row;
+  }
+  .list-group-horizontal-md > .list-group-item:first-child {
+    border-bottom-left-radius: 0.25rem;
+    border-top-right-radius: 0;
+  }
+  .list-group-horizontal-md > .list-group-item:last-child {
+    border-top-right-radius: 0.25rem;
+    border-bottom-left-radius: 0;
+  }
+  .list-group-horizontal-md > .list-group-item.active {
+    margin-top: 0;
+  }
+  .list-group-horizontal-md > .list-group-item + .list-group-item {
+    border-top-width: 1px;
+    border-left-width: 0;
+  }
+  .list-group-horizontal-md > .list-group-item + .list-group-item.active {
+    margin-left: -1px;
+    border-left-width: 1px;
+  }
+}
+
+@media (min-width: 992px) {
+  .list-group-horizontal-lg {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-direction: row;
+            flex-direction: row;
+  }
+  .list-group-horizontal-lg > .list-group-item:first-child {
+    border-bottom-left-radius: 0.25rem;
+    border-top-right-radius: 0;
+  }
+  .list-group-horizontal-lg > .list-group-item:last-child {
+    border-top-right-radius: 0.25rem;
+    border-bottom-left-radius: 0;
+  }
+  .list-group-horizontal-lg > .list-group-item.active {
+    margin-top: 0;
+  }
+  .list-group-horizontal-lg > .list-group-item + .list-group-item {
+    border-top-width: 1px;
+    border-left-width: 0;
+  }
+  .list-group-horizontal-lg > .list-group-item + .list-group-item.active {
+    margin-left: -1px;
+    border-left-width: 1px;
+  }
+}
+
+@media (min-width: 1200px) {
+  .list-group-horizontal-xl {
+    -webkit-box-orient: horizontal;
+    -webkit-box-direction: normal;
+        -ms-flex-direction: row;
+            flex-direction: row;
+  }
+  .list-group-horizontal-xl > .list-group-item:first-child {
+    border-bottom-left-radius: 0.25rem;
+    border-top-right-radius: 0;
+  }
+  .list-group-horizontal-xl > .list-group-item:last-child {
+    border-top-right-radius: 0.25rem;
+    border-bottom-left-radius: 0;
+  }
+  .list-group-horizontal-xl > .list-group-item.active {
+    margin-top: 0;
+  }
+  .list-group-horizontal-xl > .list-group-item + .list-group-item {
+    border-top-width: 1px;
+    border-left-width: 0;
+  }
+  .list-group-horizontal-xl > .list-group-item + .list-group-item.active {
+    margin-left: -1px;
+    border-left-width: 1px;
+  }
+}
+
+.list-group-flush {
+  border-radius: 0;
+}
+
+.list-group-flush > .list-group-item {
+  border-width: 0 0 1px;
+}
+
+.list-group-flush > .list-group-item:last-child {
+  border-bottom-width: 0;
+}
+
+.list-group-item-primary {
+  color: #004085;
+  background-color: #b8daff;
+}
+
+.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {
+  color: #004085;
+  background-color: #9fcdff;
+}
+
+.list-group-item-primary.list-group-item-action.active {
+  color: #fff;
+  background-color: #004085;
+  border-color: #004085;
+}
+
+.list-group-item-secondary {
+  color: #383d41;
+  background-color: #d6d8db;
+}
+
+.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {
+  color: #383d41;
+  background-color: #c8cbcf;
+}
+
+.list-group-item-secondary.list-group-item-action.active {
+  color: #fff;
+  background-color: #383d41;
+  border-color: #383d41;
+}
+
+.list-group-item-success {
+  color: #155724;
+  background-color: #c3e6cb;
+}
+
+.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {
+  color: #155724;
+  background-color: #b1dfbb;
+}
+
+.list-group-item-success.list-group-item-action.active {
+  color: #fff;
+  background-color: #155724;
+  border-color: #155724;
+}
+
+.list-group-item-info {
+  color: #0c5460;
+  background-color: #bee5eb;
+}
+
+.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {
+  color: #0c5460;
+  background-color: #abdde5;
+}
+
+.list-group-item-info.list-group-item-action.active {
+  color: #fff;
+  background-color: #0c5460;
+  border-color: #0c5460;
+}
+
+.list-group-item-warning {
+  color: #856404;
+  background-color: #ffeeba;
+}
+
+.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {
+  color: #856404;
+  background-color: #ffe8a1;
+}
+
+.list-group-item-warning.list-group-item-action.active {
+  color: #fff;
+  background-color: #856404;
+  border-color: #856404;
+}
+
+.list-group-item-danger {
+  color: #721c24;
+  background-color: #f5c6cb;
+}
+
+.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {
+  color: #721c24;
+  background-color: #f1b0b7;
+}
+
+.list-group-item-danger.list-group-item-action.active {
+  color: #fff;
+  background-color: #721c24;
+  border-color: #721c24;
+}
+
+.list-group-item-light {
+  color: #818182;
+  background-color: #fdfdfe;
+}
+
+.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {
+  color: #818182;
+  background-color: #ececf6;
+}
+
+.list-group-item-light.list-group-item-action.active {
+  color: #fff;
+  background-color: #818182;
+  border-color: #818182;
+}
+
+.list-group-item-dark {
+  color: #1b1e21;
+  background-color: #c6c8ca;
+}
+
+.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {
+  color: #1b1e21;
+  background-color: #b9bbbe;
+}
+
+.list-group-item-dark.list-group-item-action.active {
+  color: #fff;
+  background-color: #1b1e21;
+  border-color: #1b1e21;
+}
+
+.close {
+  float: right;
+  font-size: 1.5rem;
+  font-weight: 700;
+  line-height: 1;
+  color: #000;
+  text-shadow: 0 1px 0 #fff;
+  opacity: .5;
+}
+
+.close:hover {
+  color: #000;
+  text-decoration: none;
+}
+
+.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus {
+  opacity: .75;
+}
+
+button.close {
+  padding: 0;
+  background-color: transparent;
+  border: 0;
+}
+
+a.close.disabled {
+  pointer-events: none;
+}
+
+.toast {
+  -ms-flex-preferred-size: 350px;
+      flex-basis: 350px;
+  max-width: 350px;
+  font-size: 0.875rem;
+  background-color: rgba(255, 255, 255, 0.85);
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.1);
+  -webkit-box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);
+          box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);
+  opacity: 0;
+  border-radius: 0.25rem;
+}
+
+.toast:not(:last-child) {
+  margin-bottom: 0.75rem;
+}
+
+.toast.showing {
+  opacity: 1;
+}
+
+.toast.show {
+  display: block;
+  opacity: 1;
+}
+
+.toast.hide {
+  display: none;
+}
+
+.toast-header {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+  padding: 0.25rem 0.75rem;
+  color: #6c757d;
+  background-color: rgba(255, 255, 255, 0.85);
+  background-clip: padding-box;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.05);
+  border-top-left-radius: calc(0.25rem - 1px);
+  border-top-right-radius: calc(0.25rem - 1px);
+}
+
+.toast-body {
+  padding: 0.75rem;
+}
+
+.modal-open {
+  overflow: hidden;
+}
+
+.modal-open .modal {
+  overflow-x: hidden;
+  overflow-y: auto;
+}
+
+.modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 1050;
+  display: none;
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+  outline: 0;
+}
+
+.modal-dialog {
+  position: relative;
+  width: auto;
+  margin: 0.5rem;
+  pointer-events: none;
+}
+
+.modal.fade .modal-dialog {
+  -webkit-transition: -webkit-transform 0.3s ease-out;
+  transition: -webkit-transform 0.3s ease-out;
+  transition: transform 0.3s ease-out;
+  transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out;
+  -webkit-transform: translate(0, -50px);
+          transform: translate(0, -50px);
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .modal.fade .modal-dialog {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.modal.show .modal-dialog {
+  -webkit-transform: none;
+          transform: none;
+}
+
+.modal.modal-static .modal-dialog {
+  -webkit-transform: scale(1.02);
+          transform: scale(1.02);
+}
+
+.modal-dialog-scrollable {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  max-height: calc(100% - 1rem);
+}
+
+.modal-dialog-scrollable .modal-content {
+  max-height: calc(100vh - 1rem);
+  overflow: hidden;
+}
+
+.modal-dialog-scrollable .modal-header,
+.modal-dialog-scrollable .modal-footer {
+  -ms-flex-negative: 0;
+      flex-shrink: 0;
+}
+
+.modal-dialog-scrollable .modal-body {
+  overflow-y: auto;
+}
+
+.modal-dialog-centered {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+  min-height: calc(100% - 1rem);
+}
+
+.modal-dialog-centered::before {
+  display: block;
+  height: calc(100vh - 1rem);
+  height: -webkit-min-content;
+  height: -moz-min-content;
+  height: min-content;
+  content: "";
+}
+
+.modal-dialog-centered.modal-dialog-scrollable {
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: column;
+          flex-direction: column;
+  -webkit-box-pack: center;
+      -ms-flex-pack: center;
+          justify-content: center;
+  height: 100%;
+}
+
+.modal-dialog-centered.modal-dialog-scrollable .modal-content {
+  max-height: none;
+}
+
+.modal-dialog-centered.modal-dialog-scrollable::before {
+  content: none;
+}
+
+.modal-content {
+  position: relative;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-orient: vertical;
+  -webkit-box-direction: normal;
+      -ms-flex-direction: column;
+          flex-direction: column;
+  width: 100%;
+  pointer-events: auto;
+  background-color: #fff;
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 0.3rem;
+  outline: 0;
+}
+
+.modal-backdrop {
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 1040;
+  width: 100vw;
+  height: 100vh;
+  background-color: #000;
+}
+
+.modal-backdrop.fade {
+  opacity: 0;
+}
+
+.modal-backdrop.show {
+  opacity: 0.5;
+}
+
+.modal-header {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: start;
+      -ms-flex-align: start;
+          align-items: flex-start;
+  -webkit-box-pack: justify;
+      -ms-flex-pack: justify;
+          justify-content: space-between;
+  padding: 1rem 1rem;
+  border-bottom: 1px solid #dee2e6;
+  border-top-left-radius: calc(0.3rem - 1px);
+  border-top-right-radius: calc(0.3rem - 1px);
+}
+
+.modal-header .close {
+  padding: 1rem 1rem;
+  margin: -1rem -1rem -1rem auto;
+}
+
+.modal-title {
+  margin-bottom: 0;
+  line-height: 1.5;
+}
+
+.modal-body {
+  position: relative;
+  -webkit-box-flex: 1;
+      -ms-flex: 1 1 auto;
+          flex: 1 1 auto;
+  padding: 1rem;
+}
+
+.modal-footer {
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -ms-flex-wrap: wrap;
+      flex-wrap: wrap;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+  -webkit-box-pack: end;
+      -ms-flex-pack: end;
+          justify-content: flex-end;
+  padding: 0.75rem;
+  border-top: 1px solid #dee2e6;
+  border-bottom-right-radius: calc(0.3rem - 1px);
+  border-bottom-left-radius: calc(0.3rem - 1px);
+}
+
+.modal-footer > * {
+  margin: 0.25rem;
+}
+
+.modal-scrollbar-measure {
+  position: absolute;
+  top: -9999px;
+  width: 50px;
+  height: 50px;
+  overflow: scroll;
+}
+
+@media (min-width: 576px) {
+  .modal-dialog {
+    max-width: 500px;
+    margin: 1.75rem auto;
+  }
+  .modal-dialog-scrollable {
+    max-height: calc(100% - 3.5rem);
+  }
+  .modal-dialog-scrollable .modal-content {
+    max-height: calc(100vh - 3.5rem);
+  }
+  .modal-dialog-centered {
+    min-height: calc(100% - 3.5rem);
+  }
+  .modal-dialog-centered::before {
+    height: calc(100vh - 3.5rem);
+    height: -webkit-min-content;
+    height: -moz-min-content;
+    height: min-content;
+  }
+  .modal-sm {
+    max-width: 300px;
+  }
+}
+
+@media (min-width: 992px) {
+  .modal-lg,
+  .modal-xl {
+    max-width: 800px;
+  }
+}
+
+@media (min-width: 1200px) {
+  .modal-xl {
+    max-width: 1140px;
+  }
+}
+
+.tooltip {
+  position: absolute;
+  z-index: 1070;
+  display: block;
+  margin: 0;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-style: normal;
+  font-weight: 400;
+  line-height: 1.5;
+  text-align: left;
+  text-align: start;
+  text-decoration: none;
+  text-shadow: none;
+  text-transform: none;
+  letter-spacing: normal;
+  word-break: normal;
+  word-spacing: normal;
+  white-space: normal;
+  line-break: auto;
+  font-size: 0.875rem;
+  word-wrap: break-word;
+  opacity: 0;
+}
+
+.tooltip.show {
+  opacity: 0.9;
+}
+
+.tooltip .arrow {
+  position: absolute;
+  display: block;
+  width: 0.8rem;
+  height: 0.4rem;
+}
+
+.tooltip .arrow::before {
+  position: absolute;
+  content: "";
+  border-color: transparent;
+  border-style: solid;
+}
+
+.bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] {
+  padding: 0.4rem 0;
+}
+
+.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow {
+  bottom: 0;
+}
+
+.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before {
+  top: 0;
+  border-width: 0.4rem 0.4rem 0;
+  border-top-color: #000;
+}
+
+.bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] {
+  padding: 0 0.4rem;
+}
+
+.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow {
+  left: 0;
+  width: 0.4rem;
+  height: 0.8rem;
+}
+
+.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before {
+  right: 0;
+  border-width: 0.4rem 0.4rem 0.4rem 0;
+  border-right-color: #000;
+}
+
+.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] {
+  padding: 0.4rem 0;
+}
+
+.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow {
+  top: 0;
+}
+
+.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before {
+  bottom: 0;
+  border-width: 0 0.4rem 0.4rem;
+  border-bottom-color: #000;
+}
+
+.bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] {
+  padding: 0 0.4rem;
+}
+
+.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow {
+  right: 0;
+  width: 0.4rem;
+  height: 0.8rem;
+}
+
+.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before {
+  left: 0;
+  border-width: 0.4rem 0 0.4rem 0.4rem;
+  border-left-color: #000;
+}
+
+.tooltip-inner {
+  max-width: 200px;
+  padding: 0.25rem 0.5rem;
+  color: #fff;
+  text-align: center;
+  background-color: #000;
+  border-radius: 0.25rem;
+}
+
+.popover {
+  position: absolute;
+  top: 0;
+  left: 0;
+  z-index: 1060;
+  display: block;
+  max-width: 276px;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
+  font-style: normal;
+  font-weight: 400;
+  line-height: 1.5;
+  text-align: left;
+  text-align: start;
+  text-decoration: none;
+  text-shadow: none;
+  text-transform: none;
+  letter-spacing: normal;
+  word-break: normal;
+  word-spacing: normal;
+  white-space: normal;
+  line-break: auto;
+  font-size: 0.875rem;
+  word-wrap: break-word;
+  background-color: #fff;
+  background-clip: padding-box;
+  border: 1px solid rgba(0, 0, 0, 0.2);
+  border-radius: 0.3rem;
+}
+
+.popover .arrow {
+  position: absolute;
+  display: block;
+  width: 1rem;
+  height: 0.5rem;
+  margin: 0 0.3rem;
+}
+
+.popover .arrow::before, .popover .arrow::after {
+  position: absolute;
+  display: block;
+  content: "";
+  border-color: transparent;
+  border-style: solid;
+}
+
+.bs-popover-top, .bs-popover-auto[x-placement^="top"] {
+  margin-bottom: 0.5rem;
+}
+
+.bs-popover-top > .arrow, .bs-popover-auto[x-placement^="top"] > .arrow {
+  bottom: calc(-0.5rem - 1px);
+}
+
+.bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^="top"] > .arrow::before {
+  bottom: 0;
+  border-width: 0.5rem 0.5rem 0;
+  border-top-color: rgba(0, 0, 0, 0.25);
+}
+
+.bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^="top"] > .arrow::after {
+  bottom: 1px;
+  border-width: 0.5rem 0.5rem 0;
+  border-top-color: #fff;
+}
+
+.bs-popover-right, .bs-popover-auto[x-placement^="right"] {
+  margin-left: 0.5rem;
+}
+
+.bs-popover-right > .arrow, .bs-popover-auto[x-placement^="right"] > .arrow {
+  left: calc(-0.5rem - 1px);
+  width: 0.5rem;
+  height: 1rem;
+  margin: 0.3rem 0;
+}
+
+.bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^="right"] > .arrow::before {
+  left: 0;
+  border-width: 0.5rem 0.5rem 0.5rem 0;
+  border-right-color: rgba(0, 0, 0, 0.25);
+}
+
+.bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^="right"] > .arrow::after {
+  left: 1px;
+  border-width: 0.5rem 0.5rem 0.5rem 0;
+  border-right-color: #fff;
+}
+
+.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] {
+  margin-top: 0.5rem;
+}
+
+.bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^="bottom"] > .arrow {
+  top: calc(-0.5rem - 1px);
+}
+
+.bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^="bottom"] > .arrow::before {
+  top: 0;
+  border-width: 0 0.5rem 0.5rem 0.5rem;
+  border-bottom-color: rgba(0, 0, 0, 0.25);
+}
+
+.bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^="bottom"] > .arrow::after {
+  top: 1px;
+  border-width: 0 0.5rem 0.5rem 0.5rem;
+  border-bottom-color: #fff;
+}
+
+.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before {
+  position: absolute;
+  top: 0;
+  left: 50%;
+  display: block;
+  width: 1rem;
+  margin-left: -0.5rem;
+  content: "";
+  border-bottom: 1px solid #f7f7f7;
+}
+
+.bs-popover-left, .bs-popover-auto[x-placement^="left"] {
+  margin-right: 0.5rem;
+}
+
+.bs-popover-left > .arrow, .bs-popover-auto[x-placement^="left"] > .arrow {
+  right: calc(-0.5rem - 1px);
+  width: 0.5rem;
+  height: 1rem;
+  margin: 0.3rem 0;
+}
+
+.bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^="left"] > .arrow::before {
+  right: 0;
+  border-width: 0.5rem 0 0.5rem 0.5rem;
+  border-left-color: rgba(0, 0, 0, 0.25);
+}
+
+.bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^="left"] > .arrow::after {
+  right: 1px;
+  border-width: 0.5rem 0 0.5rem 0.5rem;
+  border-left-color: #fff;
+}
+
+.popover-header {
+  padding: 0.5rem 0.75rem;
+  margin-bottom: 0;
+  font-size: 1rem;
+  background-color: #f7f7f7;
+  border-bottom: 1px solid #ebebeb;
+  border-top-left-radius: calc(0.3rem - 1px);
+  border-top-right-radius: calc(0.3rem - 1px);
+}
+
+.popover-header:empty {
+  display: none;
+}
+
+.popover-body {
+  padding: 0.5rem 0.75rem;
+  color: #212529;
+}
+
+.carousel {
+  position: relative;
+}
+
+.carousel.pointer-event {
+  -ms-touch-action: pan-y;
+      touch-action: pan-y;
+}
+
+.carousel-inner {
+  position: relative;
+  width: 100%;
+  overflow: hidden;
+}
+
+.carousel-inner::after {
+  display: block;
+  clear: both;
+  content: "";
+}
+
+.carousel-item {
+  position: relative;
+  display: none;
+  float: left;
+  width: 100%;
+  margin-right: -100%;
+  -webkit-backface-visibility: hidden;
+          backface-visibility: hidden;
+  -webkit-transition: -webkit-transform 0.6s ease-in-out;
+  transition: -webkit-transform 0.6s ease-in-out;
+  transition: transform 0.6s ease-in-out;
+  transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .carousel-item {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.carousel-item.active,
+.carousel-item-next,
+.carousel-item-prev {
+  display: block;
+}
+
+.carousel-item-next:not(.carousel-item-left),
+.active.carousel-item-right {
+  -webkit-transform: translateX(100%);
+          transform: translateX(100%);
+}
+
+.carousel-item-prev:not(.carousel-item-right),
+.active.carousel-item-left {
+  -webkit-transform: translateX(-100%);
+          transform: translateX(-100%);
+}
+
+.carousel-fade .carousel-item {
+  opacity: 0;
+  -webkit-transition-property: opacity;
+  transition-property: opacity;
+  -webkit-transform: none;
+          transform: none;
+}
+
+.carousel-fade .carousel-item.active,
+.carousel-fade .carousel-item-next.carousel-item-left,
+.carousel-fade .carousel-item-prev.carousel-item-right {
+  z-index: 1;
+  opacity: 1;
+}
+
+.carousel-fade .active.carousel-item-left,
+.carousel-fade .active.carousel-item-right {
+  z-index: 0;
+  opacity: 0;
+  -webkit-transition: opacity 0s 0.6s;
+  transition: opacity 0s 0.6s;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .carousel-fade .active.carousel-item-left,
+  .carousel-fade .active.carousel-item-right {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.carousel-control-prev,
+.carousel-control-next {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  z-index: 1;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-align: center;
+      -ms-flex-align: center;
+          align-items: center;
+  -webkit-box-pack: center;
+      -ms-flex-pack: center;
+          justify-content: center;
+  width: 15%;
+  color: #fff;
+  text-align: center;
+  opacity: 0.5;
+  -webkit-transition: opacity 0.15s ease;
+  transition: opacity 0.15s ease;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .carousel-control-prev,
+  .carousel-control-next {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.carousel-control-prev:hover, .carousel-control-prev:focus,
+.carousel-control-next:hover,
+.carousel-control-next:focus {
+  color: #fff;
+  text-decoration: none;
+  outline: 0;
+  opacity: 0.9;
+}
+
+.carousel-control-prev {
+  left: 0;
+}
+
+.carousel-control-next {
+  right: 0;
+}
+
+.carousel-control-prev-icon,
+.carousel-control-next-icon {
+  display: inline-block;
+  width: 20px;
+  height: 20px;
+  background: 50% / 100% 100% no-repeat;
+}
+
+.carousel-control-prev-icon {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e");
+}
+
+.carousel-control-next-icon {
+  background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e");
+}
+
+.carousel-indicators {
+  position: absolute;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 15;
+  display: -webkit-box;
+  display: -ms-flexbox;
+  display: flex;
+  -webkit-box-pack: center;
+      -ms-flex-pack: center;
+          justify-content: center;
+  padding-left: 0;
+  margin-right: 15%;
+  margin-left: 15%;
+  list-style: none;
+}
+
+.carousel-indicators li {
+  -webkit-box-sizing: content-box;
+          box-sizing: content-box;
+  -webkit-box-flex: 0;
+      -ms-flex: 0 1 auto;
+          flex: 0 1 auto;
+  width: 30px;
+  height: 3px;
+  margin-right: 3px;
+  margin-left: 3px;
+  text-indent: -999px;
+  cursor: pointer;
+  background-color: #fff;
+  background-clip: padding-box;
+  border-top: 10px solid transparent;
+  border-bottom: 10px solid transparent;
+  opacity: .5;
+  -webkit-transition: opacity 0.6s ease;
+  transition: opacity 0.6s ease;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .carousel-indicators li {
+    -webkit-transition: none;
+    transition: none;
+  }
+}
+
+.carousel-indicators .active {
+  opacity: 1;
+}
+
+.carousel-caption {
+  position: absolute;
+  right: 15%;
+  bottom: 20px;
+  left: 15%;
+  z-index: 10;
+  padding-top: 20px;
+  padding-bottom: 20px;
+  color: #fff;
+  text-align: center;
+}
+
+@-webkit-keyframes spinner-border {
+  to {
+    -webkit-transform: rotate(360deg);
+            transform: rotate(360deg);
+  }
+}
+
+@keyframes spinner-border {
+  to {
+    -webkit-transform: rotate(360deg);
+            transform: rotate(360deg);
+  }
+}
+
+.spinner-border {
+  display: inline-block;
+  width: 2rem;
+  height: 2rem;
+  vertical-align: text-bottom;
+  border: 0.25em solid currentColor;
+  border-right-color: transparent;
+  border-radius: 50%;
+  -webkit-animation: .75s linear infinite spinner-border;
+          animation: .75s linear infinite spinner-border;
+}
+
+.spinner-border-sm {
+  width: 1rem;
+  height: 1rem;
+  border-width: 0.2em;
+}
+
+@-webkit-keyframes spinner-grow {
+  0% {
+    -webkit-transform: scale(0);
+            transform: scale(0);
+  }
+  50% {
+    opacity: 1;
+    -webkit-transform: none;
+            transform: none;
+  }
+}
+
+@keyframes spinner-grow {
+  0% {
+    -webkit-transform: scale(0);
+            transform: scale(0);
+  }
+  50% {
+    opacity: 1;
+    -webkit-transform: none;
+            transform: none;
+  }
+}
+
+.spinner-grow {
+  display: inline-block;
+  width: 2rem;
+  height: 2rem;
+  vertical-align: text-bottom;
+  background-color: currentColor;
+  border-radius: 50%;
+  opacity: 0;
+  -webkit-animation: .75s linear infinite spinner-grow;
+          animation: .75s linear infinite spinner-grow;
+}
+
+.spinner-grow-sm {
+  width: 1rem;
+  height: 1rem;
+}
+
+@media (prefers-reduced-motion: reduce) {
+  .spinner-border,
+  .spinner-grow {
+    -webkit-animation-duration: 1.5s;
+            animation-duration: 1.5s;
+  }
+}
+
+.align-baseline {
+  vertical-align: baseline !important;
+}
+
+.align-top {
+  vertical-align: top !important;
+}
+
+.align-middle {
+  vertical-align: middle !important;
+}
+
+.align-bottom {
+  vertical-align: bottom !important;
+}
+
+.align-text-bottom {
+  vertical-align: text-bottom !important;
+}
+
+.align-text-top {
+  vertical-align: text-top !important;
+}
+
+.bg-primary {
+  background-color: #007bff !important;
+}
+
+a.bg-primary:hover, a.bg-primary:focus,
+button.bg-primary:hover,
+button.bg-primary:focus {
+  background-color: #0062cc !important;
+}
+
+.bg-secondary {
+  background-color: #6c757d !important;
+}
+
+a.bg-secondary:hover, a.bg-secondary:focus,
+button.bg-secondary:hover,
+button.bg-secondary:focus {
+  background-color: #545b62 !important;
+}
+
+.bg-success {
+  background-color: #28a745 !important;
+}
+
+a.bg-success:hover, a.bg-success:focus,
+button.bg-success:hover,
+button.bg-success:focus {
+  background-color: #1e7e34 !important;
+}
+
+.bg-info {
+  background-color: #17a2b8 !important;
+}
+
+a.bg-info:hover, a.bg-info:focus,
+button.bg-info:hover,
+button.bg-info:focus {
+  background-color: #117a8b !important;
+}
+
+.bg-warning {
+  background-color: #ffc107 !important;
+}
+
+a.bg-warning:hover, a.bg-warning:focus,
+button.bg-warning:hover,
+button.bg-warning:focus {
+  background-color: #d39e00 !important;
+}
+
+.bg-danger {
+  background-color: #dc3545 !important;
+}
+
+a.bg-danger:hover, a.bg-danger:focus,
+button.bg-danger:hover,
+button.bg-danger:focus {
+  background-color: #bd2130 !important;
+}
+
+.bg-light {
+  background-color: #f8f9fa !important;
+}
+
+a.bg-light:hover, a.bg-light:focus,
+button.bg-light:hover,
+button.bg-light:focus {
+  background-color: #dae0e5 !important;
+}
+
+.bg-dark {
+  background-color: #343a40 !important;
+}
+
+a.bg-dark:hover, a.bg-dark:focus,
+button.bg-dark:hover,
+button.bg-dark:focus {
+  background-color: #1d2124 !important;
+}
+
+.bg-white {
+  background-color: #fff !important;
+}
+
+.bg-transparent {
+  background-color: transparent !important;
+}
+
+.border {
+  border: 1px solid #dee2e6 !important;
+}
+
+.border-top {
+  border-top: 1px solid #dee2e6 !important;
+}
+
+.border-right {
+  border-right: 1px solid #dee2e6 !important;
+}
+
+.border-bottom {
+  border-bottom: 1px solid #dee2e6 !important;
+}
+
+.border-left {
+  border-left: 1px solid #dee2e6 !important;
+}
+
+.border-0 {
+  border: 0 !important;
+}
+
+.border-top-0 {
+  border-top: 0 !important;
+}
+
+.border-right-0 {
+  border-right: 0 !important;
+}
+
+.border-bottom-0 {
+  border-bottom: 0 !important;
+}
+
+.border-left-0 {
+  border-left: 0 !important;
+}
+
+.border-primary {
+  border-color: #007bff !important;
+}
+
+.border-secondary {
+  border-color: #6c757d !important;
+}
+
+.border-success {
+  border-color: #28a745 !important;
+}
+
+.border-info {
+  border-color: #17a2b8 !important;
+}
+
+.border-warning {
+  border-color: #ffc107 !important;
+}
+
+.border-danger {
+  border-color: #dc3545 !important;
+}
+
+.border-light {
+  border-color: #f8f9fa !important;
+}
+
+.border-dark {
+  border-color: #343a40 !important;
+}
+
+.border-white {
+  border-color: #fff !important;
+}
+
+.rounded-sm {
+  border-radius: 0.2rem !important;
+}
+
+.rounded {
+  border-radius: 0.25rem !important;
+}
+
+.rounded-top {
+  border-top-left-radius: 0.25rem !important;
+  border-top-right-radius: 0.25rem !important;
+}
+
+.rounded-right {
+  border-top-right-radius: 0.25rem !important;
+  border-bottom-right-radius: 0.25rem !important;
+}
+
+.rounded-bottom {
+  border-bottom-right-radius: 0.25rem !important;
+  border-bottom-left-radius: 0.25rem !important;
+}
+
+.rounded-left {
+  border-top-left-radius: 0.25rem !important;
+  border-bottom-left-radius: 0.25rem !important;
+}
+
+.rounded-lg {
+  border-radius: 0.3rem !important;
+}
+
+.rounded-circle {
+  border-radius: 50% !important;
+}
+
+.rounded-pill {
+  border-radius: 50rem !important;
+}
+
+.rounded-0 {
+  border-radius: 0 !important;
+}
+
+.clearfix::after {
+  display: block;
+  clear: both;
+  content: "";
+}
+
+.d-none {
+  display: none !important;
+}
+
+.d-inline {
+  display: inline !important;
+}
+
+.d-inline-block {
+  display: inline-block !important;
+}
+
+.d-block {
+  display: block !important;
+}
+
+.d-table {
+  display: table !important;
+}
+
+.d-table-row {
+  display: table-row !important;
+}
+
+.d-table-cell {
+  display: table-cell !important;
+}
+
+.d-flex {
+  display: -webkit-box !important;
+  display: -ms-flexbox !important;
+  display: flex !important;
+}
+
+.d-inline-flex {
+  display: -webkit-inline-box !important;
+  display: -ms-inline-flexbox !important;
+  display: inline-flex !important;
+}
+
+@media (min-width: 576px) {
+  .d-sm-none {
+    display: none !important;
+  }
+  .d-sm-inline {
+    display: inline !important;
+  }
+  .d-sm-inline-block {
+    display: inline-block !important;
+  }
+  .d-sm-block {
+    display: block !important;
+  }
+  .d-sm-table {
+    display: table !important;
+  }
+  .d-sm-table-row {
+    display: table-row !important;
+  }
+  .d-sm-table-cell {
+    display: table-cell !important;
+  }
+  .d-sm-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-sm-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+@media (min-width: 768px) {
+  .d-md-none {
+    display: none !important;
+  }
+  .d-md-inline {
+    display: inline !important;
+  }
+  .d-md-inline-block {
+    display: inline-block !important;
+  }
+  .d-md-block {
+    display: block !important;
+  }
+  .d-md-table {
+    display: table !important;
+  }
+  .d-md-table-row {
+    display: table-row !important;
+  }
+  .d-md-table-cell {
+    display: table-cell !important;
+  }
+  .d-md-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-md-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .d-lg-none {
+    display: none !important;
+  }
+  .d-lg-inline {
+    display: inline !important;
+  }
+  .d-lg-inline-block {
+    display: inline-block !important;
+  }
+  .d-lg-block {
+    display: block !important;
+  }
+  .d-lg-table {
+    display: table !important;
+  }
+  .d-lg-table-row {
+    display: table-row !important;
+  }
+  .d-lg-table-cell {
+    display: table-cell !important;
+  }
+  .d-lg-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-lg-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .d-xl-none {
+    display: none !important;
+  }
+  .d-xl-inline {
+    display: inline !important;
+  }
+  .d-xl-inline-block {
+    display: inline-block !important;
+  }
+  .d-xl-block {
+    display: block !important;
+  }
+  .d-xl-table {
+    display: table !important;
+  }
+  .d-xl-table-row {
+    display: table-row !important;
+  }
+  .d-xl-table-cell {
+    display: table-cell !important;
+  }
+  .d-xl-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-xl-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+@media print {
+  .d-print-none {
+    display: none !important;
+  }
+  .d-print-inline {
+    display: inline !important;
+  }
+  .d-print-inline-block {
+    display: inline-block !important;
+  }
+  .d-print-block {
+    display: block !important;
+  }
+  .d-print-table {
+    display: table !important;
+  }
+  .d-print-table-row {
+    display: table-row !important;
+  }
+  .d-print-table-cell {
+    display: table-cell !important;
+  }
+  .d-print-flex {
+    display: -webkit-box !important;
+    display: -ms-flexbox !important;
+    display: flex !important;
+  }
+  .d-print-inline-flex {
+    display: -webkit-inline-box !important;
+    display: -ms-inline-flexbox !important;
+    display: inline-flex !important;
+  }
+}
+
+.embed-responsive {
+  position: relative;
+  display: block;
+  width: 100%;
+  padding: 0;
+  overflow: hidden;
+}
+
+.embed-responsive::before {
+  display: block;
+  content: "";
+}
+
+.embed-responsive .embed-responsive-item,
+.embed-responsive iframe,
+.embed-responsive embed,
+.embed-responsive object,
+.embed-responsive video {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  border: 0;
+}
+
+.embed-responsive-21by9::before {
+  padding-top: 42.85714%;
+}
+
+.embed-responsive-16by9::before {
+  padding-top: 56.25%;
+}
+
+.embed-responsive-4by3::before {
+  padding-top: 75%;
+}
+
+.embed-responsive-1by1::before {
+  padding-top: 100%;
+}
+
+.flex-row {
+  -webkit-box-orient: horizontal !important;
+  -webkit-box-direction: normal !important;
+      -ms-flex-direction: row !important;
+          flex-direction: row !important;
+}
+
+.flex-column {
+  -webkit-box-orient: vertical !important;
+  -webkit-box-direction: normal !important;
+      -ms-flex-direction: column !important;
+          flex-direction: column !important;
+}
+
+.flex-row-reverse {
+  -webkit-box-orient: horizontal !important;
+  -webkit-box-direction: reverse !important;
+      -ms-flex-direction: row-reverse !important;
+          flex-direction: row-reverse !important;
+}
+
+.flex-column-reverse {
+  -webkit-box-orient: vertical !important;
+  -webkit-box-direction: reverse !important;
+      -ms-flex-direction: column-reverse !important;
+          flex-direction: column-reverse !important;
+}
+
+.flex-wrap {
+  -ms-flex-wrap: wrap !important;
+      flex-wrap: wrap !important;
+}
+
+.flex-nowrap {
+  -ms-flex-wrap: nowrap !important;
+      flex-wrap: nowrap !important;
+}
+
+.flex-wrap-reverse {
+  -ms-flex-wrap: wrap-reverse !important;
+      flex-wrap: wrap-reverse !important;
+}
+
+.flex-fill {
+  -webkit-box-flex: 1 !important;
+      -ms-flex: 1 1 auto !important;
+          flex: 1 1 auto !important;
+}
+
+.flex-grow-0 {
+  -webkit-box-flex: 0 !important;
+      -ms-flex-positive: 0 !important;
+          flex-grow: 0 !important;
+}
+
+.flex-grow-1 {
+  -webkit-box-flex: 1 !important;
+      -ms-flex-positive: 1 !important;
+          flex-grow: 1 !important;
+}
+
+.flex-shrink-0 {
+  -ms-flex-negative: 0 !important;
+      flex-shrink: 0 !important;
+}
+
+.flex-shrink-1 {
+  -ms-flex-negative: 1 !important;
+      flex-shrink: 1 !important;
+}
+
+.justify-content-start {
+  -webkit-box-pack: start !important;
+      -ms-flex-pack: start !important;
+          justify-content: flex-start !important;
+}
+
+.justify-content-end {
+  -webkit-box-pack: end !important;
+      -ms-flex-pack: end !important;
+          justify-content: flex-end !important;
+}
+
+.justify-content-center {
+  -webkit-box-pack: center !important;
+      -ms-flex-pack: center !important;
+          justify-content: center !important;
+}
+
+.justify-content-between {
+  -webkit-box-pack: justify !important;
+      -ms-flex-pack: justify !important;
+          justify-content: space-between !important;
+}
+
+.justify-content-around {
+  -ms-flex-pack: distribute !important;
+      justify-content: space-around !important;
+}
+
+.align-items-start {
+  -webkit-box-align: start !important;
+      -ms-flex-align: start !important;
+          align-items: flex-start !important;
+}
+
+.align-items-end {
+  -webkit-box-align: end !important;
+      -ms-flex-align: end !important;
+          align-items: flex-end !important;
+}
+
+.align-items-center {
+  -webkit-box-align: center !important;
+      -ms-flex-align: center !important;
+          align-items: center !important;
+}
+
+.align-items-baseline {
+  -webkit-box-align: baseline !important;
+      -ms-flex-align: baseline !important;
+          align-items: baseline !important;
+}
+
+.align-items-stretch {
+  -webkit-box-align: stretch !important;
+      -ms-flex-align: stretch !important;
+          align-items: stretch !important;
+}
+
+.align-content-start {
+  -ms-flex-line-pack: start !important;
+      align-content: flex-start !important;
+}
+
+.align-content-end {
+  -ms-flex-line-pack: end !important;
+      align-content: flex-end !important;
+}
+
+.align-content-center {
+  -ms-flex-line-pack: center !important;
+      align-content: center !important;
+}
+
+.align-content-between {
+  -ms-flex-line-pack: justify !important;
+      align-content: space-between !important;
+}
+
+.align-content-around {
+  -ms-flex-line-pack: distribute !important;
+      align-content: space-around !important;
+}
+
+.align-content-stretch {
+  -ms-flex-line-pack: stretch !important;
+      align-content: stretch !important;
+}
+
+.align-self-auto {
+  -ms-flex-item-align: auto !important;
+      -ms-grid-row-align: auto !important;
+      align-self: auto !important;
+}
+
+.align-self-start {
+  -ms-flex-item-align: start !important;
+      align-self: flex-start !important;
+}
+
+.align-self-end {
+  -ms-flex-item-align: end !important;
+      align-self: flex-end !important;
+}
+
+.align-self-center {
+  -ms-flex-item-align: center !important;
+      -ms-grid-row-align: center !important;
+      align-self: center !important;
+}
+
+.align-self-baseline {
+  -ms-flex-item-align: baseline !important;
+      align-self: baseline !important;
+}
+
+.align-self-stretch {
+  -ms-flex-item-align: stretch !important;
+      -ms-grid-row-align: stretch !important;
+      align-self: stretch !important;
+}
+
+@media (min-width: 576px) {
+  .flex-sm-row {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: row !important;
+            flex-direction: row !important;
+  }
+  .flex-sm-column {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: column !important;
+            flex-direction: column !important;
+  }
+  .flex-sm-row-reverse {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: row-reverse !important;
+            flex-direction: row-reverse !important;
+  }
+  .flex-sm-column-reverse {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: column-reverse !important;
+            flex-direction: column-reverse !important;
+  }
+  .flex-sm-wrap {
+    -ms-flex-wrap: wrap !important;
+        flex-wrap: wrap !important;
+  }
+  .flex-sm-nowrap {
+    -ms-flex-wrap: nowrap !important;
+        flex-wrap: nowrap !important;
+  }
+  .flex-sm-wrap-reverse {
+    -ms-flex-wrap: wrap-reverse !important;
+        flex-wrap: wrap-reverse !important;
+  }
+  .flex-sm-fill {
+    -webkit-box-flex: 1 !important;
+        -ms-flex: 1 1 auto !important;
+            flex: 1 1 auto !important;
+  }
+  .flex-sm-grow-0 {
+    -webkit-box-flex: 0 !important;
+        -ms-flex-positive: 0 !important;
+            flex-grow: 0 !important;
+  }
+  .flex-sm-grow-1 {
+    -webkit-box-flex: 1 !important;
+        -ms-flex-positive: 1 !important;
+            flex-grow: 1 !important;
+  }
+  .flex-sm-shrink-0 {
+    -ms-flex-negative: 0 !important;
+        flex-shrink: 0 !important;
+  }
+  .flex-sm-shrink-1 {
+    -ms-flex-negative: 1 !important;
+        flex-shrink: 1 !important;
+  }
+  .justify-content-sm-start {
+    -webkit-box-pack: start !important;
+        -ms-flex-pack: start !important;
+            justify-content: flex-start !important;
+  }
+  .justify-content-sm-end {
+    -webkit-box-pack: end !important;
+        -ms-flex-pack: end !important;
+            justify-content: flex-end !important;
+  }
+  .justify-content-sm-center {
+    -webkit-box-pack: center !important;
+        -ms-flex-pack: center !important;
+            justify-content: center !important;
+  }
+  .justify-content-sm-between {
+    -webkit-box-pack: justify !important;
+        -ms-flex-pack: justify !important;
+            justify-content: space-between !important;
+  }
+  .justify-content-sm-around {
+    -ms-flex-pack: distribute !important;
+        justify-content: space-around !important;
+  }
+  .align-items-sm-start {
+    -webkit-box-align: start !important;
+        -ms-flex-align: start !important;
+            align-items: flex-start !important;
+  }
+  .align-items-sm-end {
+    -webkit-box-align: end !important;
+        -ms-flex-align: end !important;
+            align-items: flex-end !important;
+  }
+  .align-items-sm-center {
+    -webkit-box-align: center !important;
+        -ms-flex-align: center !important;
+            align-items: center !important;
+  }
+  .align-items-sm-baseline {
+    -webkit-box-align: baseline !important;
+        -ms-flex-align: baseline !important;
+            align-items: baseline !important;
+  }
+  .align-items-sm-stretch {
+    -webkit-box-align: stretch !important;
+        -ms-flex-align: stretch !important;
+            align-items: stretch !important;
+  }
+  .align-content-sm-start {
+    -ms-flex-line-pack: start !important;
+        align-content: flex-start !important;
+  }
+  .align-content-sm-end {
+    -ms-flex-line-pack: end !important;
+        align-content: flex-end !important;
+  }
+  .align-content-sm-center {
+    -ms-flex-line-pack: center !important;
+        align-content: center !important;
+  }
+  .align-content-sm-between {
+    -ms-flex-line-pack: justify !important;
+        align-content: space-between !important;
+  }
+  .align-content-sm-around {
+    -ms-flex-line-pack: distribute !important;
+        align-content: space-around !important;
+  }
+  .align-content-sm-stretch {
+    -ms-flex-line-pack: stretch !important;
+        align-content: stretch !important;
+  }
+  .align-self-sm-auto {
+    -ms-flex-item-align: auto !important;
+        -ms-grid-row-align: auto !important;
+        align-self: auto !important;
+  }
+  .align-self-sm-start {
+    -ms-flex-item-align: start !important;
+        align-self: flex-start !important;
+  }
+  .align-self-sm-end {
+    -ms-flex-item-align: end !important;
+        align-self: flex-end !important;
+  }
+  .align-self-sm-center {
+    -ms-flex-item-align: center !important;
+        -ms-grid-row-align: center !important;
+        align-self: center !important;
+  }
+  .align-self-sm-baseline {
+    -ms-flex-item-align: baseline !important;
+        align-self: baseline !important;
+  }
+  .align-self-sm-stretch {
+    -ms-flex-item-align: stretch !important;
+        -ms-grid-row-align: stretch !important;
+        align-self: stretch !important;
+  }
+}
+
+@media (min-width: 768px) {
+  .flex-md-row {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: row !important;
+            flex-direction: row !important;
+  }
+  .flex-md-column {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: column !important;
+            flex-direction: column !important;
+  }
+  .flex-md-row-reverse {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: row-reverse !important;
+            flex-direction: row-reverse !important;
+  }
+  .flex-md-column-reverse {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: column-reverse !important;
+            flex-direction: column-reverse !important;
+  }
+  .flex-md-wrap {
+    -ms-flex-wrap: wrap !important;
+        flex-wrap: wrap !important;
+  }
+  .flex-md-nowrap {
+    -ms-flex-wrap: nowrap !important;
+        flex-wrap: nowrap !important;
+  }
+  .flex-md-wrap-reverse {
+    -ms-flex-wrap: wrap-reverse !important;
+        flex-wrap: wrap-reverse !important;
+  }
+  .flex-md-fill {
+    -webkit-box-flex: 1 !important;
+        -ms-flex: 1 1 auto !important;
+            flex: 1 1 auto !important;
+  }
+  .flex-md-grow-0 {
+    -webkit-box-flex: 0 !important;
+        -ms-flex-positive: 0 !important;
+            flex-grow: 0 !important;
+  }
+  .flex-md-grow-1 {
+    -webkit-box-flex: 1 !important;
+        -ms-flex-positive: 1 !important;
+            flex-grow: 1 !important;
+  }
+  .flex-md-shrink-0 {
+    -ms-flex-negative: 0 !important;
+        flex-shrink: 0 !important;
+  }
+  .flex-md-shrink-1 {
+    -ms-flex-negative: 1 !important;
+        flex-shrink: 1 !important;
+  }
+  .justify-content-md-start {
+    -webkit-box-pack: start !important;
+        -ms-flex-pack: start !important;
+            justify-content: flex-start !important;
+  }
+  .justify-content-md-end {
+    -webkit-box-pack: end !important;
+        -ms-flex-pack: end !important;
+            justify-content: flex-end !important;
+  }
+  .justify-content-md-center {
+    -webkit-box-pack: center !important;
+        -ms-flex-pack: center !important;
+            justify-content: center !important;
+  }
+  .justify-content-md-between {
+    -webkit-box-pack: justify !important;
+        -ms-flex-pack: justify !important;
+            justify-content: space-between !important;
+  }
+  .justify-content-md-around {
+    -ms-flex-pack: distribute !important;
+        justify-content: space-around !important;
+  }
+  .align-items-md-start {
+    -webkit-box-align: start !important;
+        -ms-flex-align: start !important;
+            align-items: flex-start !important;
+  }
+  .align-items-md-end {
+    -webkit-box-align: end !important;
+        -ms-flex-align: end !important;
+            align-items: flex-end !important;
+  }
+  .align-items-md-center {
+    -webkit-box-align: center !important;
+        -ms-flex-align: center !important;
+            align-items: center !important;
+  }
+  .align-items-md-baseline {
+    -webkit-box-align: baseline !important;
+        -ms-flex-align: baseline !important;
+            align-items: baseline !important;
+  }
+  .align-items-md-stretch {
+    -webkit-box-align: stretch !important;
+        -ms-flex-align: stretch !important;
+            align-items: stretch !important;
+  }
+  .align-content-md-start {
+    -ms-flex-line-pack: start !important;
+        align-content: flex-start !important;
+  }
+  .align-content-md-end {
+    -ms-flex-line-pack: end !important;
+        align-content: flex-end !important;
+  }
+  .align-content-md-center {
+    -ms-flex-line-pack: center !important;
+        align-content: center !important;
+  }
+  .align-content-md-between {
+    -ms-flex-line-pack: justify !important;
+        align-content: space-between !important;
+  }
+  .align-content-md-around {
+    -ms-flex-line-pack: distribute !important;
+        align-content: space-around !important;
+  }
+  .align-content-md-stretch {
+    -ms-flex-line-pack: stretch !important;
+        align-content: stretch !important;
+  }
+  .align-self-md-auto {
+    -ms-flex-item-align: auto !important;
+        -ms-grid-row-align: auto !important;
+        align-self: auto !important;
+  }
+  .align-self-md-start {
+    -ms-flex-item-align: start !important;
+        align-self: flex-start !important;
+  }
+  .align-self-md-end {
+    -ms-flex-item-align: end !important;
+        align-self: flex-end !important;
+  }
+  .align-self-md-center {
+    -ms-flex-item-align: center !important;
+        -ms-grid-row-align: center !important;
+        align-self: center !important;
+  }
+  .align-self-md-baseline {
+    -ms-flex-item-align: baseline !important;
+        align-self: baseline !important;
+  }
+  .align-self-md-stretch {
+    -ms-flex-item-align: stretch !important;
+        -ms-grid-row-align: stretch !important;
+        align-self: stretch !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .flex-lg-row {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: row !important;
+            flex-direction: row !important;
+  }
+  .flex-lg-column {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: column !important;
+            flex-direction: column !important;
+  }
+  .flex-lg-row-reverse {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: row-reverse !important;
+            flex-direction: row-reverse !important;
+  }
+  .flex-lg-column-reverse {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: column-reverse !important;
+            flex-direction: column-reverse !important;
+  }
+  .flex-lg-wrap {
+    -ms-flex-wrap: wrap !important;
+        flex-wrap: wrap !important;
+  }
+  .flex-lg-nowrap {
+    -ms-flex-wrap: nowrap !important;
+        flex-wrap: nowrap !important;
+  }
+  .flex-lg-wrap-reverse {
+    -ms-flex-wrap: wrap-reverse !important;
+        flex-wrap: wrap-reverse !important;
+  }
+  .flex-lg-fill {
+    -webkit-box-flex: 1 !important;
+        -ms-flex: 1 1 auto !important;
+            flex: 1 1 auto !important;
+  }
+  .flex-lg-grow-0 {
+    -webkit-box-flex: 0 !important;
+        -ms-flex-positive: 0 !important;
+            flex-grow: 0 !important;
+  }
+  .flex-lg-grow-1 {
+    -webkit-box-flex: 1 !important;
+        -ms-flex-positive: 1 !important;
+            flex-grow: 1 !important;
+  }
+  .flex-lg-shrink-0 {
+    -ms-flex-negative: 0 !important;
+        flex-shrink: 0 !important;
+  }
+  .flex-lg-shrink-1 {
+    -ms-flex-negative: 1 !important;
+        flex-shrink: 1 !important;
+  }
+  .justify-content-lg-start {
+    -webkit-box-pack: start !important;
+        -ms-flex-pack: start !important;
+            justify-content: flex-start !important;
+  }
+  .justify-content-lg-end {
+    -webkit-box-pack: end !important;
+        -ms-flex-pack: end !important;
+            justify-content: flex-end !important;
+  }
+  .justify-content-lg-center {
+    -webkit-box-pack: center !important;
+        -ms-flex-pack: center !important;
+            justify-content: center !important;
+  }
+  .justify-content-lg-between {
+    -webkit-box-pack: justify !important;
+        -ms-flex-pack: justify !important;
+            justify-content: space-between !important;
+  }
+  .justify-content-lg-around {
+    -ms-flex-pack: distribute !important;
+        justify-content: space-around !important;
+  }
+  .align-items-lg-start {
+    -webkit-box-align: start !important;
+        -ms-flex-align: start !important;
+            align-items: flex-start !important;
+  }
+  .align-items-lg-end {
+    -webkit-box-align: end !important;
+        -ms-flex-align: end !important;
+            align-items: flex-end !important;
+  }
+  .align-items-lg-center {
+    -webkit-box-align: center !important;
+        -ms-flex-align: center !important;
+            align-items: center !important;
+  }
+  .align-items-lg-baseline {
+    -webkit-box-align: baseline !important;
+        -ms-flex-align: baseline !important;
+            align-items: baseline !important;
+  }
+  .align-items-lg-stretch {
+    -webkit-box-align: stretch !important;
+        -ms-flex-align: stretch !important;
+            align-items: stretch !important;
+  }
+  .align-content-lg-start {
+    -ms-flex-line-pack: start !important;
+        align-content: flex-start !important;
+  }
+  .align-content-lg-end {
+    -ms-flex-line-pack: end !important;
+        align-content: flex-end !important;
+  }
+  .align-content-lg-center {
+    -ms-flex-line-pack: center !important;
+        align-content: center !important;
+  }
+  .align-content-lg-between {
+    -ms-flex-line-pack: justify !important;
+        align-content: space-between !important;
+  }
+  .align-content-lg-around {
+    -ms-flex-line-pack: distribute !important;
+        align-content: space-around !important;
+  }
+  .align-content-lg-stretch {
+    -ms-flex-line-pack: stretch !important;
+        align-content: stretch !important;
+  }
+  .align-self-lg-auto {
+    -ms-flex-item-align: auto !important;
+        -ms-grid-row-align: auto !important;
+        align-self: auto !important;
+  }
+  .align-self-lg-start {
+    -ms-flex-item-align: start !important;
+        align-self: flex-start !important;
+  }
+  .align-self-lg-end {
+    -ms-flex-item-align: end !important;
+        align-self: flex-end !important;
+  }
+  .align-self-lg-center {
+    -ms-flex-item-align: center !important;
+        -ms-grid-row-align: center !important;
+        align-self: center !important;
+  }
+  .align-self-lg-baseline {
+    -ms-flex-item-align: baseline !important;
+        align-self: baseline !important;
+  }
+  .align-self-lg-stretch {
+    -ms-flex-item-align: stretch !important;
+        -ms-grid-row-align: stretch !important;
+        align-self: stretch !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .flex-xl-row {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: row !important;
+            flex-direction: row !important;
+  }
+  .flex-xl-column {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: normal !important;
+        -ms-flex-direction: column !important;
+            flex-direction: column !important;
+  }
+  .flex-xl-row-reverse {
+    -webkit-box-orient: horizontal !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: row-reverse !important;
+            flex-direction: row-reverse !important;
+  }
+  .flex-xl-column-reverse {
+    -webkit-box-orient: vertical !important;
+    -webkit-box-direction: reverse !important;
+        -ms-flex-direction: column-reverse !important;
+            flex-direction: column-reverse !important;
+  }
+  .flex-xl-wrap {
+    -ms-flex-wrap: wrap !important;
+        flex-wrap: wrap !important;
+  }
+  .flex-xl-nowrap {
+    -ms-flex-wrap: nowrap !important;
+        flex-wrap: nowrap !important;
+  }
+  .flex-xl-wrap-reverse {
+    -ms-flex-wrap: wrap-reverse !important;
+        flex-wrap: wrap-reverse !important;
+  }
+  .flex-xl-fill {
+    -webkit-box-flex: 1 !important;
+        -ms-flex: 1 1 auto !important;
+            flex: 1 1 auto !important;
+  }
+  .flex-xl-grow-0 {
+    -webkit-box-flex: 0 !important;
+        -ms-flex-positive: 0 !important;
+            flex-grow: 0 !important;
+  }
+  .flex-xl-grow-1 {
+    -webkit-box-flex: 1 !important;
+        -ms-flex-positive: 1 !important;
+            flex-grow: 1 !important;
+  }
+  .flex-xl-shrink-0 {
+    -ms-flex-negative: 0 !important;
+        flex-shrink: 0 !important;
+  }
+  .flex-xl-shrink-1 {
+    -ms-flex-negative: 1 !important;
+        flex-shrink: 1 !important;
+  }
+  .justify-content-xl-start {
+    -webkit-box-pack: start !important;
+        -ms-flex-pack: start !important;
+            justify-content: flex-start !important;
+  }
+  .justify-content-xl-end {
+    -webkit-box-pack: end !important;
+        -ms-flex-pack: end !important;
+            justify-content: flex-end !important;
+  }
+  .justify-content-xl-center {
+    -webkit-box-pack: center !important;
+        -ms-flex-pack: center !important;
+            justify-content: center !important;
+  }
+  .justify-content-xl-between {
+    -webkit-box-pack: justify !important;
+        -ms-flex-pack: justify !important;
+            justify-content: space-between !important;
+  }
+  .justify-content-xl-around {
+    -ms-flex-pack: distribute !important;
+        justify-content: space-around !important;
+  }
+  .align-items-xl-start {
+    -webkit-box-align: start !important;
+        -ms-flex-align: start !important;
+            align-items: flex-start !important;
+  }
+  .align-items-xl-end {
+    -webkit-box-align: end !important;
+        -ms-flex-align: end !important;
+            align-items: flex-end !important;
+  }
+  .align-items-xl-center {
+    -webkit-box-align: center !important;
+        -ms-flex-align: center !important;
+            align-items: center !important;
+  }
+  .align-items-xl-baseline {
+    -webkit-box-align: baseline !important;
+        -ms-flex-align: baseline !important;
+            align-items: baseline !important;
+  }
+  .align-items-xl-stretch {
+    -webkit-box-align: stretch !important;
+        -ms-flex-align: stretch !important;
+            align-items: stretch !important;
+  }
+  .align-content-xl-start {
+    -ms-flex-line-pack: start !important;
+        align-content: flex-start !important;
+  }
+  .align-content-xl-end {
+    -ms-flex-line-pack: end !important;
+        align-content: flex-end !important;
+  }
+  .align-content-xl-center {
+    -ms-flex-line-pack: center !important;
+        align-content: center !important;
+  }
+  .align-content-xl-between {
+    -ms-flex-line-pack: justify !important;
+        align-content: space-between !important;
+  }
+  .align-content-xl-around {
+    -ms-flex-line-pack: distribute !important;
+        align-content: space-around !important;
+  }
+  .align-content-xl-stretch {
+    -ms-flex-line-pack: stretch !important;
+        align-content: stretch !important;
+  }
+  .align-self-xl-auto {
+    -ms-flex-item-align: auto !important;
+        -ms-grid-row-align: auto !important;
+        align-self: auto !important;
+  }
+  .align-self-xl-start {
+    -ms-flex-item-align: start !important;
+        align-self: flex-start !important;
+  }
+  .align-self-xl-end {
+    -ms-flex-item-align: end !important;
+        align-self: flex-end !important;
+  }
+  .align-self-xl-center {
+    -ms-flex-item-align: center !important;
+        -ms-grid-row-align: center !important;
+        align-self: center !important;
+  }
+  .align-self-xl-baseline {
+    -ms-flex-item-align: baseline !important;
+        align-self: baseline !important;
+  }
+  .align-self-xl-stretch {
+    -ms-flex-item-align: stretch !important;
+        -ms-grid-row-align: stretch !important;
+        align-self: stretch !important;
+  }
+}
+
+.float-left {
+  float: left !important;
+}
+
+.float-right {
+  float: right !important;
+}
+
+.float-none {
+  float: none !important;
+}
+
+@media (min-width: 576px) {
+  .float-sm-left {
+    float: left !important;
+  }
+  .float-sm-right {
+    float: right !important;
+  }
+  .float-sm-none {
+    float: none !important;
+  }
+}
+
+@media (min-width: 768px) {
+  .float-md-left {
+    float: left !important;
+  }
+  .float-md-right {
+    float: right !important;
+  }
+  .float-md-none {
+    float: none !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .float-lg-left {
+    float: left !important;
+  }
+  .float-lg-right {
+    float: right !important;
+  }
+  .float-lg-none {
+    float: none !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .float-xl-left {
+    float: left !important;
+  }
+  .float-xl-right {
+    float: right !important;
+  }
+  .float-xl-none {
+    float: none !important;
+  }
+}
+
+.user-select-all {
+  -webkit-user-select: all !important;
+     -moz-user-select: all !important;
+      -ms-user-select: all !important;
+          user-select: all !important;
+}
+
+.user-select-auto {
+  -webkit-user-select: auto !important;
+     -moz-user-select: auto !important;
+      -ms-user-select: auto !important;
+          user-select: auto !important;
+}
+
+.user-select-none {
+  -webkit-user-select: none !important;
+     -moz-user-select: none !important;
+      -ms-user-select: none !important;
+          user-select: none !important;
+}
+
+.overflow-auto {
+  overflow: auto !important;
+}
+
+.overflow-hidden {
+  overflow: hidden !important;
+}
+
+.position-static {
+  position: static !important;
+}
+
+.position-relative {
+  position: relative !important;
+}
+
+.position-absolute {
+  position: absolute !important;
+}
+
+.position-fixed {
+  position: fixed !important;
+}
+
+.position-sticky {
+  position: -webkit-sticky !important;
+  position: sticky !important;
+}
+
+.fixed-top {
+  position: fixed;
+  top: 0;
+  right: 0;
+  left: 0;
+  z-index: 1030;
+}
+
+.fixed-bottom {
+  position: fixed;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1030;
+}
+
+@supports ((position: -webkit-sticky) or (position: sticky)) {
+  .sticky-top {
+    position: -webkit-sticky;
+    position: sticky;
+    top: 0;
+    z-index: 1020;
+  }
+}
+
+.sr-only {
+  position: absolute;
+  width: 1px;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  white-space: nowrap;
+  border: 0;
+}
+
+.sr-only-focusable:active, .sr-only-focusable:focus {
+  position: static;
+  width: auto;
+  height: auto;
+  overflow: visible;
+  clip: auto;
+  white-space: normal;
+}
+
+.shadow-sm {
+  -webkit-box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
+          box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;
+}
+
+.shadow {
+  -webkit-box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
+          box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;
+}
+
+.shadow-lg {
+  -webkit-box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;
+          box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;
+}
+
+.shadow-none {
+  -webkit-box-shadow: none !important;
+          box-shadow: none !important;
+}
+
+.w-25 {
+  width: 25% !important;
+}
+
+.w-50 {
+  width: 50% !important;
+}
+
+.w-75 {
+  width: 75% !important;
+}
+
+.w-100 {
+  width: 100% !important;
+}
+
+.w-auto {
+  width: auto !important;
+}
+
+.h-25 {
+  height: 25% !important;
+}
+
+.h-50 {
+  height: 50% !important;
+}
+
+.h-75 {
+  height: 75% !important;
+}
+
+.h-100 {
+  height: 100% !important;
+}
+
+.h-auto {
+  height: auto !important;
+}
+
+.mw-100 {
+  max-width: 100% !important;
+}
+
+.mh-100 {
+  max-height: 100% !important;
+}
+
+.min-vw-100 {
+  min-width: 100vw !important;
+}
+
+.min-vh-100 {
+  min-height: 100vh !important;
+}
+
+.vw-100 {
+  width: 100vw !important;
+}
+
+.vh-100 {
+  height: 100vh !important;
+}
+
+.m-0 {
+  margin: 0 !important;
+}
+
+.mt-0,
+.my-0 {
+  margin-top: 0 !important;
+}
+
+.mr-0,
+.mx-0 {
+  margin-right: 0 !important;
+}
+
+.mb-0,
+.my-0 {
+  margin-bottom: 0 !important;
+}
+
+.ml-0,
+.mx-0 {
+  margin-left: 0 !important;
+}
+
+.m-1 {
+  margin: 0.25rem !important;
+}
+
+.mt-1,
+.my-1 {
+  margin-top: 0.25rem !important;
+}
+
+.mr-1,
+.mx-1 {
+  margin-right: 0.25rem !important;
+}
+
+.mb-1,
+.my-1 {
+  margin-bottom: 0.25rem !important;
+}
+
+.ml-1,
+.mx-1 {
+  margin-left: 0.25rem !important;
+}
+
+.m-2 {
+  margin: 0.5rem !important;
+}
+
+.mt-2,
+.my-2 {
+  margin-top: 0.5rem !important;
+}
+
+.mr-2,
+.mx-2 {
+  margin-right: 0.5rem !important;
+}
+
+.mb-2,
+.my-2 {
+  margin-bottom: 0.5rem !important;
+}
+
+.ml-2,
+.mx-2 {
+  margin-left: 0.5rem !important;
+}
+
+.m-3 {
+  margin: 1rem !important;
+}
+
+.mt-3,
+.my-3 {
+  margin-top: 1rem !important;
+}
+
+.mr-3,
+.mx-3 {
+  margin-right: 1rem !important;
+}
+
+.mb-3,
+.my-3 {
+  margin-bottom: 1rem !important;
+}
+
+.ml-3,
+.mx-3 {
+  margin-left: 1rem !important;
+}
+
+.m-4 {
+  margin: 1.5rem !important;
+}
+
+.mt-4,
+.my-4 {
+  margin-top: 1.5rem !important;
+}
+
+.mr-4,
+.mx-4 {
+  margin-right: 1.5rem !important;
+}
+
+.mb-4,
+.my-4 {
+  margin-bottom: 1.5rem !important;
+}
+
+.ml-4,
+.mx-4 {
+  margin-left: 1.5rem !important;
+}
+
+.m-5 {
+  margin: 3rem !important;
+}
+
+.mt-5,
+.my-5 {
+  margin-top: 3rem !important;
+}
+
+.mr-5,
+.mx-5 {
+  margin-right: 3rem !important;
+}
+
+.mb-5,
+.my-5 {
+  margin-bottom: 3rem !important;
+}
+
+.ml-5,
+.mx-5 {
+  margin-left: 3rem !important;
+}
+
+.p-0 {
+  padding: 0 !important;
+}
+
+.pt-0,
+.py-0 {
+  padding-top: 0 !important;
+}
+
+.pr-0,
+.px-0 {
+  padding-right: 0 !important;
+}
+
+.pb-0,
+.py-0 {
+  padding-bottom: 0 !important;
+}
+
+.pl-0,
+.px-0 {
+  padding-left: 0 !important;
+}
+
+.p-1 {
+  padding: 0.25rem !important;
+}
+
+.pt-1,
+.py-1 {
+  padding-top: 0.25rem !important;
+}
+
+.pr-1,
+.px-1 {
+  padding-right: 0.25rem !important;
+}
+
+.pb-1,
+.py-1 {
+  padding-bottom: 0.25rem !important;
+}
+
+.pl-1,
+.px-1 {
+  padding-left: 0.25rem !important;
+}
+
+.p-2 {
+  padding: 0.5rem !important;
+}
+
+.pt-2,
+.py-2 {
+  padding-top: 0.5rem !important;
+}
+
+.pr-2,
+.px-2 {
+  padding-right: 0.5rem !important;
+}
+
+.pb-2,
+.py-2 {
+  padding-bottom: 0.5rem !important;
+}
+
+.pl-2,
+.px-2 {
+  padding-left: 0.5rem !important;
+}
+
+.p-3 {
+  padding: 1rem !important;
+}
+
+.pt-3,
+.py-3 {
+  padding-top: 1rem !important;
+}
+
+.pr-3,
+.px-3 {
+  padding-right: 1rem !important;
+}
+
+.pb-3,
+.py-3 {
+  padding-bottom: 1rem !important;
+}
+
+.pl-3,
+.px-3 {
+  padding-left: 1rem !important;
+}
+
+.p-4 {
+  padding: 1.5rem !important;
+}
+
+.pt-4,
+.py-4 {
+  padding-top: 1.5rem !important;
+}
+
+.pr-4,
+.px-4 {
+  padding-right: 1.5rem !important;
+}
+
+.pb-4,
+.py-4 {
+  padding-bottom: 1.5rem !important;
+}
+
+.pl-4,
+.px-4 {
+  padding-left: 1.5rem !important;
+}
+
+.p-5 {
+  padding: 3rem !important;
+}
+
+.pt-5,
+.py-5 {
+  padding-top: 3rem !important;
+}
+
+.pr-5,
+.px-5 {
+  padding-right: 3rem !important;
+}
+
+.pb-5,
+.py-5 {
+  padding-bottom: 3rem !important;
+}
+
+.pl-5,
+.px-5 {
+  padding-left: 3rem !important;
+}
+
+.m-n1 {
+  margin: -0.25rem !important;
+}
+
+.mt-n1,
+.my-n1 {
+  margin-top: -0.25rem !important;
+}
+
+.mr-n1,
+.mx-n1 {
+  margin-right: -0.25rem !important;
+}
+
+.mb-n1,
+.my-n1 {
+  margin-bottom: -0.25rem !important;
+}
+
+.ml-n1,
+.mx-n1 {
+  margin-left: -0.25rem !important;
+}
+
+.m-n2 {
+  margin: -0.5rem !important;
+}
+
+.mt-n2,
+.my-n2 {
+  margin-top: -0.5rem !important;
+}
+
+.mr-n2,
+.mx-n2 {
+  margin-right: -0.5rem !important;
+}
+
+.mb-n2,
+.my-n2 {
+  margin-bottom: -0.5rem !important;
+}
+
+.ml-n2,
+.mx-n2 {
+  margin-left: -0.5rem !important;
+}
+
+.m-n3 {
+  margin: -1rem !important;
+}
+
+.mt-n3,
+.my-n3 {
+  margin-top: -1rem !important;
+}
+
+.mr-n3,
+.mx-n3 {
+  margin-right: -1rem !important;
+}
+
+.mb-n3,
+.my-n3 {
+  margin-bottom: -1rem !important;
+}
+
+.ml-n3,
+.mx-n3 {
+  margin-left: -1rem !important;
+}
+
+.m-n4 {
+  margin: -1.5rem !important;
+}
+
+.mt-n4,
+.my-n4 {
+  margin-top: -1.5rem !important;
+}
+
+.mr-n4,
+.mx-n4 {
+  margin-right: -1.5rem !important;
+}
+
+.mb-n4,
+.my-n4 {
+  margin-bottom: -1.5rem !important;
+}
+
+.ml-n4,
+.mx-n4 {
+  margin-left: -1.5rem !important;
+}
+
+.m-n5 {
+  margin: -3rem !important;
+}
+
+.mt-n5,
+.my-n5 {
+  margin-top: -3rem !important;
+}
+
+.mr-n5,
+.mx-n5 {
+  margin-right: -3rem !important;
+}
+
+.mb-n5,
+.my-n5 {
+  margin-bottom: -3rem !important;
+}
+
+.ml-n5,
+.mx-n5 {
+  margin-left: -3rem !important;
+}
+
+.m-auto {
+  margin: auto !important;
+}
+
+.mt-auto,
+.my-auto {
+  margin-top: auto !important;
+}
+
+.mr-auto,
+.mx-auto {
+  margin-right: auto !important;
+}
+
+.mb-auto,
+.my-auto {
+  margin-bottom: auto !important;
+}
+
+.ml-auto,
+.mx-auto {
+  margin-left: auto !important;
+}
+
+@media (min-width: 576px) {
+  .m-sm-0 {
+    margin: 0 !important;
+  }
+  .mt-sm-0,
+  .my-sm-0 {
+    margin-top: 0 !important;
+  }
+  .mr-sm-0,
+  .mx-sm-0 {
+    margin-right: 0 !important;
+  }
+  .mb-sm-0,
+  .my-sm-0 {
+    margin-bottom: 0 !important;
+  }
+  .ml-sm-0,
+  .mx-sm-0 {
+    margin-left: 0 !important;
+  }
+  .m-sm-1 {
+    margin: 0.25rem !important;
+  }
+  .mt-sm-1,
+  .my-sm-1 {
+    margin-top: 0.25rem !important;
+  }
+  .mr-sm-1,
+  .mx-sm-1 {
+    margin-right: 0.25rem !important;
+  }
+  .mb-sm-1,
+  .my-sm-1 {
+    margin-bottom: 0.25rem !important;
+  }
+  .ml-sm-1,
+  .mx-sm-1 {
+    margin-left: 0.25rem !important;
+  }
+  .m-sm-2 {
+    margin: 0.5rem !important;
+  }
+  .mt-sm-2,
+  .my-sm-2 {
+    margin-top: 0.5rem !important;
+  }
+  .mr-sm-2,
+  .mx-sm-2 {
+    margin-right: 0.5rem !important;
+  }
+  .mb-sm-2,
+  .my-sm-2 {
+    margin-bottom: 0.5rem !important;
+  }
+  .ml-sm-2,
+  .mx-sm-2 {
+    margin-left: 0.5rem !important;
+  }
+  .m-sm-3 {
+    margin: 1rem !important;
+  }
+  .mt-sm-3,
+  .my-sm-3 {
+    margin-top: 1rem !important;
+  }
+  .mr-sm-3,
+  .mx-sm-3 {
+    margin-right: 1rem !important;
+  }
+  .mb-sm-3,
+  .my-sm-3 {
+    margin-bottom: 1rem !important;
+  }
+  .ml-sm-3,
+  .mx-sm-3 {
+    margin-left: 1rem !important;
+  }
+  .m-sm-4 {
+    margin: 1.5rem !important;
+  }
+  .mt-sm-4,
+  .my-sm-4 {
+    margin-top: 1.5rem !important;
+  }
+  .mr-sm-4,
+  .mx-sm-4 {
+    margin-right: 1.5rem !important;
+  }
+  .mb-sm-4,
+  .my-sm-4 {
+    margin-bottom: 1.5rem !important;
+  }
+  .ml-sm-4,
+  .mx-sm-4 {
+    margin-left: 1.5rem !important;
+  }
+  .m-sm-5 {
+    margin: 3rem !important;
+  }
+  .mt-sm-5,
+  .my-sm-5 {
+    margin-top: 3rem !important;
+  }
+  .mr-sm-5,
+  .mx-sm-5 {
+    margin-right: 3rem !important;
+  }
+  .mb-sm-5,
+  .my-sm-5 {
+    margin-bottom: 3rem !important;
+  }
+  .ml-sm-5,
+  .mx-sm-5 {
+    margin-left: 3rem !important;
+  }
+  .p-sm-0 {
+    padding: 0 !important;
+  }
+  .pt-sm-0,
+  .py-sm-0 {
+    padding-top: 0 !important;
+  }
+  .pr-sm-0,
+  .px-sm-0 {
+    padding-right: 0 !important;
+  }
+  .pb-sm-0,
+  .py-sm-0 {
+    padding-bottom: 0 !important;
+  }
+  .pl-sm-0,
+  .px-sm-0 {
+    padding-left: 0 !important;
+  }
+  .p-sm-1 {
+    padding: 0.25rem !important;
+  }
+  .pt-sm-1,
+  .py-sm-1 {
+    padding-top: 0.25rem !important;
+  }
+  .pr-sm-1,
+  .px-sm-1 {
+    padding-right: 0.25rem !important;
+  }
+  .pb-sm-1,
+  .py-sm-1 {
+    padding-bottom: 0.25rem !important;
+  }
+  .pl-sm-1,
+  .px-sm-1 {
+    padding-left: 0.25rem !important;
+  }
+  .p-sm-2 {
+    padding: 0.5rem !important;
+  }
+  .pt-sm-2,
+  .py-sm-2 {
+    padding-top: 0.5rem !important;
+  }
+  .pr-sm-2,
+  .px-sm-2 {
+    padding-right: 0.5rem !important;
+  }
+  .pb-sm-2,
+  .py-sm-2 {
+    padding-bottom: 0.5rem !important;
+  }
+  .pl-sm-2,
+  .px-sm-2 {
+    padding-left: 0.5rem !important;
+  }
+  .p-sm-3 {
+    padding: 1rem !important;
+  }
+  .pt-sm-3,
+  .py-sm-3 {
+    padding-top: 1rem !important;
+  }
+  .pr-sm-3,
+  .px-sm-3 {
+    padding-right: 1rem !important;
+  }
+  .pb-sm-3,
+  .py-sm-3 {
+    padding-bottom: 1rem !important;
+  }
+  .pl-sm-3,
+  .px-sm-3 {
+    padding-left: 1rem !important;
+  }
+  .p-sm-4 {
+    padding: 1.5rem !important;
+  }
+  .pt-sm-4,
+  .py-sm-4 {
+    padding-top: 1.5rem !important;
+  }
+  .pr-sm-4,
+  .px-sm-4 {
+    padding-right: 1.5rem !important;
+  }
+  .pb-sm-4,
+  .py-sm-4 {
+    padding-bottom: 1.5rem !important;
+  }
+  .pl-sm-4,
+  .px-sm-4 {
+    padding-left: 1.5rem !important;
+  }
+  .p-sm-5 {
+    padding: 3rem !important;
+  }
+  .pt-sm-5,
+  .py-sm-5 {
+    padding-top: 3rem !important;
+  }
+  .pr-sm-5,
+  .px-sm-5 {
+    padding-right: 3rem !important;
+  }
+  .pb-sm-5,
+  .py-sm-5 {
+    padding-bottom: 3rem !important;
+  }
+  .pl-sm-5,
+  .px-sm-5 {
+    padding-left: 3rem !important;
+  }
+  .m-sm-n1 {
+    margin: -0.25rem !important;
+  }
+  .mt-sm-n1,
+  .my-sm-n1 {
+    margin-top: -0.25rem !important;
+  }
+  .mr-sm-n1,
+  .mx-sm-n1 {
+    margin-right: -0.25rem !important;
+  }
+  .mb-sm-n1,
+  .my-sm-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+  .ml-sm-n1,
+  .mx-sm-n1 {
+    margin-left: -0.25rem !important;
+  }
+  .m-sm-n2 {
+    margin: -0.5rem !important;
+  }
+  .mt-sm-n2,
+  .my-sm-n2 {
+    margin-top: -0.5rem !important;
+  }
+  .mr-sm-n2,
+  .mx-sm-n2 {
+    margin-right: -0.5rem !important;
+  }
+  .mb-sm-n2,
+  .my-sm-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+  .ml-sm-n2,
+  .mx-sm-n2 {
+    margin-left: -0.5rem !important;
+  }
+  .m-sm-n3 {
+    margin: -1rem !important;
+  }
+  .mt-sm-n3,
+  .my-sm-n3 {
+    margin-top: -1rem !important;
+  }
+  .mr-sm-n3,
+  .mx-sm-n3 {
+    margin-right: -1rem !important;
+  }
+  .mb-sm-n3,
+  .my-sm-n3 {
+    margin-bottom: -1rem !important;
+  }
+  .ml-sm-n3,
+  .mx-sm-n3 {
+    margin-left: -1rem !important;
+  }
+  .m-sm-n4 {
+    margin: -1.5rem !important;
+  }
+  .mt-sm-n4,
+  .my-sm-n4 {
+    margin-top: -1.5rem !important;
+  }
+  .mr-sm-n4,
+  .mx-sm-n4 {
+    margin-right: -1.5rem !important;
+  }
+  .mb-sm-n4,
+  .my-sm-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+  .ml-sm-n4,
+  .mx-sm-n4 {
+    margin-left: -1.5rem !important;
+  }
+  .m-sm-n5 {
+    margin: -3rem !important;
+  }
+  .mt-sm-n5,
+  .my-sm-n5 {
+    margin-top: -3rem !important;
+  }
+  .mr-sm-n5,
+  .mx-sm-n5 {
+    margin-right: -3rem !important;
+  }
+  .mb-sm-n5,
+  .my-sm-n5 {
+    margin-bottom: -3rem !important;
+  }
+  .ml-sm-n5,
+  .mx-sm-n5 {
+    margin-left: -3rem !important;
+  }
+  .m-sm-auto {
+    margin: auto !important;
+  }
+  .mt-sm-auto,
+  .my-sm-auto {
+    margin-top: auto !important;
+  }
+  .mr-sm-auto,
+  .mx-sm-auto {
+    margin-right: auto !important;
+  }
+  .mb-sm-auto,
+  .my-sm-auto {
+    margin-bottom: auto !important;
+  }
+  .ml-sm-auto,
+  .mx-sm-auto {
+    margin-left: auto !important;
+  }
+}
+
+@media (min-width: 768px) {
+  .m-md-0 {
+    margin: 0 !important;
+  }
+  .mt-md-0,
+  .my-md-0 {
+    margin-top: 0 !important;
+  }
+  .mr-md-0,
+  .mx-md-0 {
+    margin-right: 0 !important;
+  }
+  .mb-md-0,
+  .my-md-0 {
+    margin-bottom: 0 !important;
+  }
+  .ml-md-0,
+  .mx-md-0 {
+    margin-left: 0 !important;
+  }
+  .m-md-1 {
+    margin: 0.25rem !important;
+  }
+  .mt-md-1,
+  .my-md-1 {
+    margin-top: 0.25rem !important;
+  }
+  .mr-md-1,
+  .mx-md-1 {
+    margin-right: 0.25rem !important;
+  }
+  .mb-md-1,
+  .my-md-1 {
+    margin-bottom: 0.25rem !important;
+  }
+  .ml-md-1,
+  .mx-md-1 {
+    margin-left: 0.25rem !important;
+  }
+  .m-md-2 {
+    margin: 0.5rem !important;
+  }
+  .mt-md-2,
+  .my-md-2 {
+    margin-top: 0.5rem !important;
+  }
+  .mr-md-2,
+  .mx-md-2 {
+    margin-right: 0.5rem !important;
+  }
+  .mb-md-2,
+  .my-md-2 {
+    margin-bottom: 0.5rem !important;
+  }
+  .ml-md-2,
+  .mx-md-2 {
+    margin-left: 0.5rem !important;
+  }
+  .m-md-3 {
+    margin: 1rem !important;
+  }
+  .mt-md-3,
+  .my-md-3 {
+    margin-top: 1rem !important;
+  }
+  .mr-md-3,
+  .mx-md-3 {
+    margin-right: 1rem !important;
+  }
+  .mb-md-3,
+  .my-md-3 {
+    margin-bottom: 1rem !important;
+  }
+  .ml-md-3,
+  .mx-md-3 {
+    margin-left: 1rem !important;
+  }
+  .m-md-4 {
+    margin: 1.5rem !important;
+  }
+  .mt-md-4,
+  .my-md-4 {
+    margin-top: 1.5rem !important;
+  }
+  .mr-md-4,
+  .mx-md-4 {
+    margin-right: 1.5rem !important;
+  }
+  .mb-md-4,
+  .my-md-4 {
+    margin-bottom: 1.5rem !important;
+  }
+  .ml-md-4,
+  .mx-md-4 {
+    margin-left: 1.5rem !important;
+  }
+  .m-md-5 {
+    margin: 3rem !important;
+  }
+  .mt-md-5,
+  .my-md-5 {
+    margin-top: 3rem !important;
+  }
+  .mr-md-5,
+  .mx-md-5 {
+    margin-right: 3rem !important;
+  }
+  .mb-md-5,
+  .my-md-5 {
+    margin-bottom: 3rem !important;
+  }
+  .ml-md-5,
+  .mx-md-5 {
+    margin-left: 3rem !important;
+  }
+  .p-md-0 {
+    padding: 0 !important;
+  }
+  .pt-md-0,
+  .py-md-0 {
+    padding-top: 0 !important;
+  }
+  .pr-md-0,
+  .px-md-0 {
+    padding-right: 0 !important;
+  }
+  .pb-md-0,
+  .py-md-0 {
+    padding-bottom: 0 !important;
+  }
+  .pl-md-0,
+  .px-md-0 {
+    padding-left: 0 !important;
+  }
+  .p-md-1 {
+    padding: 0.25rem !important;
+  }
+  .pt-md-1,
+  .py-md-1 {
+    padding-top: 0.25rem !important;
+  }
+  .pr-md-1,
+  .px-md-1 {
+    padding-right: 0.25rem !important;
+  }
+  .pb-md-1,
+  .py-md-1 {
+    padding-bottom: 0.25rem !important;
+  }
+  .pl-md-1,
+  .px-md-1 {
+    padding-left: 0.25rem !important;
+  }
+  .p-md-2 {
+    padding: 0.5rem !important;
+  }
+  .pt-md-2,
+  .py-md-2 {
+    padding-top: 0.5rem !important;
+  }
+  .pr-md-2,
+  .px-md-2 {
+    padding-right: 0.5rem !important;
+  }
+  .pb-md-2,
+  .py-md-2 {
+    padding-bottom: 0.5rem !important;
+  }
+  .pl-md-2,
+  .px-md-2 {
+    padding-left: 0.5rem !important;
+  }
+  .p-md-3 {
+    padding: 1rem !important;
+  }
+  .pt-md-3,
+  .py-md-3 {
+    padding-top: 1rem !important;
+  }
+  .pr-md-3,
+  .px-md-3 {
+    padding-right: 1rem !important;
+  }
+  .pb-md-3,
+  .py-md-3 {
+    padding-bottom: 1rem !important;
+  }
+  .pl-md-3,
+  .px-md-3 {
+    padding-left: 1rem !important;
+  }
+  .p-md-4 {
+    padding: 1.5rem !important;
+  }
+  .pt-md-4,
+  .py-md-4 {
+    padding-top: 1.5rem !important;
+  }
+  .pr-md-4,
+  .px-md-4 {
+    padding-right: 1.5rem !important;
+  }
+  .pb-md-4,
+  .py-md-4 {
+    padding-bottom: 1.5rem !important;
+  }
+  .pl-md-4,
+  .px-md-4 {
+    padding-left: 1.5rem !important;
+  }
+  .p-md-5 {
+    padding: 3rem !important;
+  }
+  .pt-md-5,
+  .py-md-5 {
+    padding-top: 3rem !important;
+  }
+  .pr-md-5,
+  .px-md-5 {
+    padding-right: 3rem !important;
+  }
+  .pb-md-5,
+  .py-md-5 {
+    padding-bottom: 3rem !important;
+  }
+  .pl-md-5,
+  .px-md-5 {
+    padding-left: 3rem !important;
+  }
+  .m-md-n1 {
+    margin: -0.25rem !important;
+  }
+  .mt-md-n1,
+  .my-md-n1 {
+    margin-top: -0.25rem !important;
+  }
+  .mr-md-n1,
+  .mx-md-n1 {
+    margin-right: -0.25rem !important;
+  }
+  .mb-md-n1,
+  .my-md-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+  .ml-md-n1,
+  .mx-md-n1 {
+    margin-left: -0.25rem !important;
+  }
+  .m-md-n2 {
+    margin: -0.5rem !important;
+  }
+  .mt-md-n2,
+  .my-md-n2 {
+    margin-top: -0.5rem !important;
+  }
+  .mr-md-n2,
+  .mx-md-n2 {
+    margin-right: -0.5rem !important;
+  }
+  .mb-md-n2,
+  .my-md-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+  .ml-md-n2,
+  .mx-md-n2 {
+    margin-left: -0.5rem !important;
+  }
+  .m-md-n3 {
+    margin: -1rem !important;
+  }
+  .mt-md-n3,
+  .my-md-n3 {
+    margin-top: -1rem !important;
+  }
+  .mr-md-n3,
+  .mx-md-n3 {
+    margin-right: -1rem !important;
+  }
+  .mb-md-n3,
+  .my-md-n3 {
+    margin-bottom: -1rem !important;
+  }
+  .ml-md-n3,
+  .mx-md-n3 {
+    margin-left: -1rem !important;
+  }
+  .m-md-n4 {
+    margin: -1.5rem !important;
+  }
+  .mt-md-n4,
+  .my-md-n4 {
+    margin-top: -1.5rem !important;
+  }
+  .mr-md-n4,
+  .mx-md-n4 {
+    margin-right: -1.5rem !important;
+  }
+  .mb-md-n4,
+  .my-md-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+  .ml-md-n4,
+  .mx-md-n4 {
+    margin-left: -1.5rem !important;
+  }
+  .m-md-n5 {
+    margin: -3rem !important;
+  }
+  .mt-md-n5,
+  .my-md-n5 {
+    margin-top: -3rem !important;
+  }
+  .mr-md-n5,
+  .mx-md-n5 {
+    margin-right: -3rem !important;
+  }
+  .mb-md-n5,
+  .my-md-n5 {
+    margin-bottom: -3rem !important;
+  }
+  .ml-md-n5,
+  .mx-md-n5 {
+    margin-left: -3rem !important;
+  }
+  .m-md-auto {
+    margin: auto !important;
+  }
+  .mt-md-auto,
+  .my-md-auto {
+    margin-top: auto !important;
+  }
+  .mr-md-auto,
+  .mx-md-auto {
+    margin-right: auto !important;
+  }
+  .mb-md-auto,
+  .my-md-auto {
+    margin-bottom: auto !important;
+  }
+  .ml-md-auto,
+  .mx-md-auto {
+    margin-left: auto !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .m-lg-0 {
+    margin: 0 !important;
+  }
+  .mt-lg-0,
+  .my-lg-0 {
+    margin-top: 0 !important;
+  }
+  .mr-lg-0,
+  .mx-lg-0 {
+    margin-right: 0 !important;
+  }
+  .mb-lg-0,
+  .my-lg-0 {
+    margin-bottom: 0 !important;
+  }
+  .ml-lg-0,
+  .mx-lg-0 {
+    margin-left: 0 !important;
+  }
+  .m-lg-1 {
+    margin: 0.25rem !important;
+  }
+  .mt-lg-1,
+  .my-lg-1 {
+    margin-top: 0.25rem !important;
+  }
+  .mr-lg-1,
+  .mx-lg-1 {
+    margin-right: 0.25rem !important;
+  }
+  .mb-lg-1,
+  .my-lg-1 {
+    margin-bottom: 0.25rem !important;
+  }
+  .ml-lg-1,
+  .mx-lg-1 {
+    margin-left: 0.25rem !important;
+  }
+  .m-lg-2 {
+    margin: 0.5rem !important;
+  }
+  .mt-lg-2,
+  .my-lg-2 {
+    margin-top: 0.5rem !important;
+  }
+  .mr-lg-2,
+  .mx-lg-2 {
+    margin-right: 0.5rem !important;
+  }
+  .mb-lg-2,
+  .my-lg-2 {
+    margin-bottom: 0.5rem !important;
+  }
+  .ml-lg-2,
+  .mx-lg-2 {
+    margin-left: 0.5rem !important;
+  }
+  .m-lg-3 {
+    margin: 1rem !important;
+  }
+  .mt-lg-3,
+  .my-lg-3 {
+    margin-top: 1rem !important;
+  }
+  .mr-lg-3,
+  .mx-lg-3 {
+    margin-right: 1rem !important;
+  }
+  .mb-lg-3,
+  .my-lg-3 {
+    margin-bottom: 1rem !important;
+  }
+  .ml-lg-3,
+  .mx-lg-3 {
+    margin-left: 1rem !important;
+  }
+  .m-lg-4 {
+    margin: 1.5rem !important;
+  }
+  .mt-lg-4,
+  .my-lg-4 {
+    margin-top: 1.5rem !important;
+  }
+  .mr-lg-4,
+  .mx-lg-4 {
+    margin-right: 1.5rem !important;
+  }
+  .mb-lg-4,
+  .my-lg-4 {
+    margin-bottom: 1.5rem !important;
+  }
+  .ml-lg-4,
+  .mx-lg-4 {
+    margin-left: 1.5rem !important;
+  }
+  .m-lg-5 {
+    margin: 3rem !important;
+  }
+  .mt-lg-5,
+  .my-lg-5 {
+    margin-top: 3rem !important;
+  }
+  .mr-lg-5,
+  .mx-lg-5 {
+    margin-right: 3rem !important;
+  }
+  .mb-lg-5,
+  .my-lg-5 {
+    margin-bottom: 3rem !important;
+  }
+  .ml-lg-5,
+  .mx-lg-5 {
+    margin-left: 3rem !important;
+  }
+  .p-lg-0 {
+    padding: 0 !important;
+  }
+  .pt-lg-0,
+  .py-lg-0 {
+    padding-top: 0 !important;
+  }
+  .pr-lg-0,
+  .px-lg-0 {
+    padding-right: 0 !important;
+  }
+  .pb-lg-0,
+  .py-lg-0 {
+    padding-bottom: 0 !important;
+  }
+  .pl-lg-0,
+  .px-lg-0 {
+    padding-left: 0 !important;
+  }
+  .p-lg-1 {
+    padding: 0.25rem !important;
+  }
+  .pt-lg-1,
+  .py-lg-1 {
+    padding-top: 0.25rem !important;
+  }
+  .pr-lg-1,
+  .px-lg-1 {
+    padding-right: 0.25rem !important;
+  }
+  .pb-lg-1,
+  .py-lg-1 {
+    padding-bottom: 0.25rem !important;
+  }
+  .pl-lg-1,
+  .px-lg-1 {
+    padding-left: 0.25rem !important;
+  }
+  .p-lg-2 {
+    padding: 0.5rem !important;
+  }
+  .pt-lg-2,
+  .py-lg-2 {
+    padding-top: 0.5rem !important;
+  }
+  .pr-lg-2,
+  .px-lg-2 {
+    padding-right: 0.5rem !important;
+  }
+  .pb-lg-2,
+  .py-lg-2 {
+    padding-bottom: 0.5rem !important;
+  }
+  .pl-lg-2,
+  .px-lg-2 {
+    padding-left: 0.5rem !important;
+  }
+  .p-lg-3 {
+    padding: 1rem !important;
+  }
+  .pt-lg-3,
+  .py-lg-3 {
+    padding-top: 1rem !important;
+  }
+  .pr-lg-3,
+  .px-lg-3 {
+    padding-right: 1rem !important;
+  }
+  .pb-lg-3,
+  .py-lg-3 {
+    padding-bottom: 1rem !important;
+  }
+  .pl-lg-3,
+  .px-lg-3 {
+    padding-left: 1rem !important;
+  }
+  .p-lg-4 {
+    padding: 1.5rem !important;
+  }
+  .pt-lg-4,
+  .py-lg-4 {
+    padding-top: 1.5rem !important;
+  }
+  .pr-lg-4,
+  .px-lg-4 {
+    padding-right: 1.5rem !important;
+  }
+  .pb-lg-4,
+  .py-lg-4 {
+    padding-bottom: 1.5rem !important;
+  }
+  .pl-lg-4,
+  .px-lg-4 {
+    padding-left: 1.5rem !important;
+  }
+  .p-lg-5 {
+    padding: 3rem !important;
+  }
+  .pt-lg-5,
+  .py-lg-5 {
+    padding-top: 3rem !important;
+  }
+  .pr-lg-5,
+  .px-lg-5 {
+    padding-right: 3rem !important;
+  }
+  .pb-lg-5,
+  .py-lg-5 {
+    padding-bottom: 3rem !important;
+  }
+  .pl-lg-5,
+  .px-lg-5 {
+    padding-left: 3rem !important;
+  }
+  .m-lg-n1 {
+    margin: -0.25rem !important;
+  }
+  .mt-lg-n1,
+  .my-lg-n1 {
+    margin-top: -0.25rem !important;
+  }
+  .mr-lg-n1,
+  .mx-lg-n1 {
+    margin-right: -0.25rem !important;
+  }
+  .mb-lg-n1,
+  .my-lg-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+  .ml-lg-n1,
+  .mx-lg-n1 {
+    margin-left: -0.25rem !important;
+  }
+  .m-lg-n2 {
+    margin: -0.5rem !important;
+  }
+  .mt-lg-n2,
+  .my-lg-n2 {
+    margin-top: -0.5rem !important;
+  }
+  .mr-lg-n2,
+  .mx-lg-n2 {
+    margin-right: -0.5rem !important;
+  }
+  .mb-lg-n2,
+  .my-lg-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+  .ml-lg-n2,
+  .mx-lg-n2 {
+    margin-left: -0.5rem !important;
+  }
+  .m-lg-n3 {
+    margin: -1rem !important;
+  }
+  .mt-lg-n3,
+  .my-lg-n3 {
+    margin-top: -1rem !important;
+  }
+  .mr-lg-n3,
+  .mx-lg-n3 {
+    margin-right: -1rem !important;
+  }
+  .mb-lg-n3,
+  .my-lg-n3 {
+    margin-bottom: -1rem !important;
+  }
+  .ml-lg-n3,
+  .mx-lg-n3 {
+    margin-left: -1rem !important;
+  }
+  .m-lg-n4 {
+    margin: -1.5rem !important;
+  }
+  .mt-lg-n4,
+  .my-lg-n4 {
+    margin-top: -1.5rem !important;
+  }
+  .mr-lg-n4,
+  .mx-lg-n4 {
+    margin-right: -1.5rem !important;
+  }
+  .mb-lg-n4,
+  .my-lg-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+  .ml-lg-n4,
+  .mx-lg-n4 {
+    margin-left: -1.5rem !important;
+  }
+  .m-lg-n5 {
+    margin: -3rem !important;
+  }
+  .mt-lg-n5,
+  .my-lg-n5 {
+    margin-top: -3rem !important;
+  }
+  .mr-lg-n5,
+  .mx-lg-n5 {
+    margin-right: -3rem !important;
+  }
+  .mb-lg-n5,
+  .my-lg-n5 {
+    margin-bottom: -3rem !important;
+  }
+  .ml-lg-n5,
+  .mx-lg-n5 {
+    margin-left: -3rem !important;
+  }
+  .m-lg-auto {
+    margin: auto !important;
+  }
+  .mt-lg-auto,
+  .my-lg-auto {
+    margin-top: auto !important;
+  }
+  .mr-lg-auto,
+  .mx-lg-auto {
+    margin-right: auto !important;
+  }
+  .mb-lg-auto,
+  .my-lg-auto {
+    margin-bottom: auto !important;
+  }
+  .ml-lg-auto,
+  .mx-lg-auto {
+    margin-left: auto !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .m-xl-0 {
+    margin: 0 !important;
+  }
+  .mt-xl-0,
+  .my-xl-0 {
+    margin-top: 0 !important;
+  }
+  .mr-xl-0,
+  .mx-xl-0 {
+    margin-right: 0 !important;
+  }
+  .mb-xl-0,
+  .my-xl-0 {
+    margin-bottom: 0 !important;
+  }
+  .ml-xl-0,
+  .mx-xl-0 {
+    margin-left: 0 !important;
+  }
+  .m-xl-1 {
+    margin: 0.25rem !important;
+  }
+  .mt-xl-1,
+  .my-xl-1 {
+    margin-top: 0.25rem !important;
+  }
+  .mr-xl-1,
+  .mx-xl-1 {
+    margin-right: 0.25rem !important;
+  }
+  .mb-xl-1,
+  .my-xl-1 {
+    margin-bottom: 0.25rem !important;
+  }
+  .ml-xl-1,
+  .mx-xl-1 {
+    margin-left: 0.25rem !important;
+  }
+  .m-xl-2 {
+    margin: 0.5rem !important;
+  }
+  .mt-xl-2,
+  .my-xl-2 {
+    margin-top: 0.5rem !important;
+  }
+  .mr-xl-2,
+  .mx-xl-2 {
+    margin-right: 0.5rem !important;
+  }
+  .mb-xl-2,
+  .my-xl-2 {
+    margin-bottom: 0.5rem !important;
+  }
+  .ml-xl-2,
+  .mx-xl-2 {
+    margin-left: 0.5rem !important;
+  }
+  .m-xl-3 {
+    margin: 1rem !important;
+  }
+  .mt-xl-3,
+  .my-xl-3 {
+    margin-top: 1rem !important;
+  }
+  .mr-xl-3,
+  .mx-xl-3 {
+    margin-right: 1rem !important;
+  }
+  .mb-xl-3,
+  .my-xl-3 {
+    margin-bottom: 1rem !important;
+  }
+  .ml-xl-3,
+  .mx-xl-3 {
+    margin-left: 1rem !important;
+  }
+  .m-xl-4 {
+    margin: 1.5rem !important;
+  }
+  .mt-xl-4,
+  .my-xl-4 {
+    margin-top: 1.5rem !important;
+  }
+  .mr-xl-4,
+  .mx-xl-4 {
+    margin-right: 1.5rem !important;
+  }
+  .mb-xl-4,
+  .my-xl-4 {
+    margin-bottom: 1.5rem !important;
+  }
+  .ml-xl-4,
+  .mx-xl-4 {
+    margin-left: 1.5rem !important;
+  }
+  .m-xl-5 {
+    margin: 3rem !important;
+  }
+  .mt-xl-5,
+  .my-xl-5 {
+    margin-top: 3rem !important;
+  }
+  .mr-xl-5,
+  .mx-xl-5 {
+    margin-right: 3rem !important;
+  }
+  .mb-xl-5,
+  .my-xl-5 {
+    margin-bottom: 3rem !important;
+  }
+  .ml-xl-5,
+  .mx-xl-5 {
+    margin-left: 3rem !important;
+  }
+  .p-xl-0 {
+    padding: 0 !important;
+  }
+  .pt-xl-0,
+  .py-xl-0 {
+    padding-top: 0 !important;
+  }
+  .pr-xl-0,
+  .px-xl-0 {
+    padding-right: 0 !important;
+  }
+  .pb-xl-0,
+  .py-xl-0 {
+    padding-bottom: 0 !important;
+  }
+  .pl-xl-0,
+  .px-xl-0 {
+    padding-left: 0 !important;
+  }
+  .p-xl-1 {
+    padding: 0.25rem !important;
+  }
+  .pt-xl-1,
+  .py-xl-1 {
+    padding-top: 0.25rem !important;
+  }
+  .pr-xl-1,
+  .px-xl-1 {
+    padding-right: 0.25rem !important;
+  }
+  .pb-xl-1,
+  .py-xl-1 {
+    padding-bottom: 0.25rem !important;
+  }
+  .pl-xl-1,
+  .px-xl-1 {
+    padding-left: 0.25rem !important;
+  }
+  .p-xl-2 {
+    padding: 0.5rem !important;
+  }
+  .pt-xl-2,
+  .py-xl-2 {
+    padding-top: 0.5rem !important;
+  }
+  .pr-xl-2,
+  .px-xl-2 {
+    padding-right: 0.5rem !important;
+  }
+  .pb-xl-2,
+  .py-xl-2 {
+    padding-bottom: 0.5rem !important;
+  }
+  .pl-xl-2,
+  .px-xl-2 {
+    padding-left: 0.5rem !important;
+  }
+  .p-xl-3 {
+    padding: 1rem !important;
+  }
+  .pt-xl-3,
+  .py-xl-3 {
+    padding-top: 1rem !important;
+  }
+  .pr-xl-3,
+  .px-xl-3 {
+    padding-right: 1rem !important;
+  }
+  .pb-xl-3,
+  .py-xl-3 {
+    padding-bottom: 1rem !important;
+  }
+  .pl-xl-3,
+  .px-xl-3 {
+    padding-left: 1rem !important;
+  }
+  .p-xl-4 {
+    padding: 1.5rem !important;
+  }
+  .pt-xl-4,
+  .py-xl-4 {
+    padding-top: 1.5rem !important;
+  }
+  .pr-xl-4,
+  .px-xl-4 {
+    padding-right: 1.5rem !important;
+  }
+  .pb-xl-4,
+  .py-xl-4 {
+    padding-bottom: 1.5rem !important;
+  }
+  .pl-xl-4,
+  .px-xl-4 {
+    padding-left: 1.5rem !important;
+  }
+  .p-xl-5 {
+    padding: 3rem !important;
+  }
+  .pt-xl-5,
+  .py-xl-5 {
+    padding-top: 3rem !important;
+  }
+  .pr-xl-5,
+  .px-xl-5 {
+    padding-right: 3rem !important;
+  }
+  .pb-xl-5,
+  .py-xl-5 {
+    padding-bottom: 3rem !important;
+  }
+  .pl-xl-5,
+  .px-xl-5 {
+    padding-left: 3rem !important;
+  }
+  .m-xl-n1 {
+    margin: -0.25rem !important;
+  }
+  .mt-xl-n1,
+  .my-xl-n1 {
+    margin-top: -0.25rem !important;
+  }
+  .mr-xl-n1,
+  .mx-xl-n1 {
+    margin-right: -0.25rem !important;
+  }
+  .mb-xl-n1,
+  .my-xl-n1 {
+    margin-bottom: -0.25rem !important;
+  }
+  .ml-xl-n1,
+  .mx-xl-n1 {
+    margin-left: -0.25rem !important;
+  }
+  .m-xl-n2 {
+    margin: -0.5rem !important;
+  }
+  .mt-xl-n2,
+  .my-xl-n2 {
+    margin-top: -0.5rem !important;
+  }
+  .mr-xl-n2,
+  .mx-xl-n2 {
+    margin-right: -0.5rem !important;
+  }
+  .mb-xl-n2,
+  .my-xl-n2 {
+    margin-bottom: -0.5rem !important;
+  }
+  .ml-xl-n2,
+  .mx-xl-n2 {
+    margin-left: -0.5rem !important;
+  }
+  .m-xl-n3 {
+    margin: -1rem !important;
+  }
+  .mt-xl-n3,
+  .my-xl-n3 {
+    margin-top: -1rem !important;
+  }
+  .mr-xl-n3,
+  .mx-xl-n3 {
+    margin-right: -1rem !important;
+  }
+  .mb-xl-n3,
+  .my-xl-n3 {
+    margin-bottom: -1rem !important;
+  }
+  .ml-xl-n3,
+  .mx-xl-n3 {
+    margin-left: -1rem !important;
+  }
+  .m-xl-n4 {
+    margin: -1.5rem !important;
+  }
+  .mt-xl-n4,
+  .my-xl-n4 {
+    margin-top: -1.5rem !important;
+  }
+  .mr-xl-n4,
+  .mx-xl-n4 {
+    margin-right: -1.5rem !important;
+  }
+  .mb-xl-n4,
+  .my-xl-n4 {
+    margin-bottom: -1.5rem !important;
+  }
+  .ml-xl-n4,
+  .mx-xl-n4 {
+    margin-left: -1.5rem !important;
+  }
+  .m-xl-n5 {
+    margin: -3rem !important;
+  }
+  .mt-xl-n5,
+  .my-xl-n5 {
+    margin-top: -3rem !important;
+  }
+  .mr-xl-n5,
+  .mx-xl-n5 {
+    margin-right: -3rem !important;
+  }
+  .mb-xl-n5,
+  .my-xl-n5 {
+    margin-bottom: -3rem !important;
+  }
+  .ml-xl-n5,
+  .mx-xl-n5 {
+    margin-left: -3rem !important;
+  }
+  .m-xl-auto {
+    margin: auto !important;
+  }
+  .mt-xl-auto,
+  .my-xl-auto {
+    margin-top: auto !important;
+  }
+  .mr-xl-auto,
+  .mx-xl-auto {
+    margin-right: auto !important;
+  }
+  .mb-xl-auto,
+  .my-xl-auto {
+    margin-bottom: auto !important;
+  }
+  .ml-xl-auto,
+  .mx-xl-auto {
+    margin-left: auto !important;
+  }
+}
+
+.stretched-link::after {
+  position: absolute;
+  top: 0;
+  right: 0;
+  bottom: 0;
+  left: 0;
+  z-index: 1;
+  pointer-events: auto;
+  content: "";
+  background-color: rgba(0, 0, 0, 0);
+}
+
+.text-monospace {
+  font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;
+}
+
+.text-justify {
+  text-align: justify !important;
+}
+
+.text-wrap {
+  white-space: normal !important;
+}
+
+.text-nowrap {
+  white-space: nowrap !important;
+}
+
+.text-truncate {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.text-left {
+  text-align: left !important;
+}
+
+.text-right {
+  text-align: right !important;
+}
+
+.text-center {
+  text-align: center !important;
+}
+
+@media (min-width: 576px) {
+  .text-sm-left {
+    text-align: left !important;
+  }
+  .text-sm-right {
+    text-align: right !important;
+  }
+  .text-sm-center {
+    text-align: center !important;
+  }
+}
+
+@media (min-width: 768px) {
+  .text-md-left {
+    text-align: left !important;
+  }
+  .text-md-right {
+    text-align: right !important;
+  }
+  .text-md-center {
+    text-align: center !important;
+  }
+}
+
+@media (min-width: 992px) {
+  .text-lg-left {
+    text-align: left !important;
+  }
+  .text-lg-right {
+    text-align: right !important;
+  }
+  .text-lg-center {
+    text-align: center !important;
+  }
+}
+
+@media (min-width: 1200px) {
+  .text-xl-left {
+    text-align: left !important;
+  }
+  .text-xl-right {
+    text-align: right !important;
+  }
+  .text-xl-center {
+    text-align: center !important;
+  }
+}
+
+.text-lowercase {
+  text-transform: lowercase !important;
+}
+
+.text-uppercase {
+  text-transform: uppercase !important;
+}
+
+.text-capitalize {
+  text-transform: capitalize !important;
+}
+
+.font-weight-light {
+  font-weight: 300 !important;
+}
+
+.font-weight-lighter {
+  font-weight: lighter !important;
+}
+
+.font-weight-normal {
+  font-weight: 400 !important;
+}
+
+.font-weight-bold {
+  font-weight: 700 !important;
+}
+
+.font-weight-bolder {
+  font-weight: bolder !important;
+}
+
+.font-italic {
+  font-style: italic !important;
+}
+
+.text-white {
+  color: #fff !important;
+}
+
+.text-primary {
+  color: #007bff !important;
+}
+
+a.text-primary:hover, a.text-primary:focus {
+  color: #0056b3 !important;
+}
+
+.text-secondary {
+  color: #6c757d !important;
+}
+
+a.text-secondary:hover, a.text-secondary:focus {
+  color: #494f54 !important;
+}
+
+.text-success {
+  color: #28a745 !important;
+}
+
+a.text-success:hover, a.text-success:focus {
+  color: #19692c !important;
+}
+
+.text-info {
+  color: #17a2b8 !important;
+}
+
+a.text-info:hover, a.text-info:focus {
+  color: #0f6674 !important;
+}
+
+.text-warning {
+  color: #ffc107 !important;
+}
+
+a.text-warning:hover, a.text-warning:focus {
+  color: #ba8b00 !important;
+}
+
+.text-danger {
+  color: #dc3545 !important;
+}
+
+a.text-danger:hover, a.text-danger:focus {
+  color: #a71d2a !important;
+}
+
+.text-light {
+  color: #f8f9fa !important;
+}
+
+a.text-light:hover, a.text-light:focus {
+  color: #cbd3da !important;
+}
+
+.text-dark {
+  color: #343a40 !important;
+}
+
+a.text-dark:hover, a.text-dark:focus {
+  color: #121416 !important;
+}
+
+.text-body {
+  color: #212529 !important;
+}
+
+.text-muted {
+  color: #6c757d !important;
+}
+
+.text-black-50 {
+  color: rgba(0, 0, 0, 0.5) !important;
+}
+
+.text-white-50 {
+  color: rgba(255, 255, 255, 0.5) !important;
+}
+
+.text-hide {
+  font: 0/0 a;
+  color: transparent;
+  text-shadow: none;
+  background-color: transparent;
+  border: 0;
+}
+
+.text-decoration-none {
+  text-decoration: none !important;
+}
+
+.text-break {
+  word-break: break-word !important;
+  word-wrap: break-word !important;
+}
+
+.text-reset {
+  color: inherit !important;
+}
+
+.visible {
+  visibility: visible !important;
+}
+
+.invisible {
+  visibility: hidden !important;
+}
+
+@media print {
+  *,
+  *::before,
+  *::after {
+    text-shadow: none !important;
+    -webkit-box-shadow: none !important;
+            box-shadow: none !important;
+  }
+  a:not(.btn) {
+    text-decoration: underline;
+  }
+  abbr[title]::after {
+    content: " (" attr(title) ")";
+  }
+  pre {
+    white-space: pre-wrap !important;
+  }
+  pre,
+  blockquote {
+    border: 1px solid #adb5bd;
+    page-break-inside: avoid;
+  }
+  thead {
+    display: table-header-group;
+  }
+  tr,
+  img {
+    page-break-inside: avoid;
+  }
+  p,
+  h2,
+  h3 {
+    orphans: 3;
+    widows: 3;
+  }
+  h2,
+  h3 {
+    page-break-after: avoid;
+  }
+  @page {
+    size: a3;
+  }
+  body {
+    min-width: 992px !important;
+  }
+  .container {
+    min-width: 992px !important;
+  }
+  .navbar {
+    display: none;
+  }
+  .badge {
+    border: 1px solid #000;
+  }
+  .table {
+    border-collapse: collapse !important;
+  }
+  .table td,
+  .table th {
+    background-color: #fff !important;
+  }
+  .table-bordered th,
+  .table-bordered td {
+    border: 1px solid #dee2e6 !important;
+  }
+  .table-dark {
+    color: inherit;
+  }
+  .table-dark th,
+  .table-dark td,
+  .table-dark thead th,
+  .table-dark tbody + tbody {
+    border-color: #dee2e6;
+  }
+  .table .thead-dark th {
+    color: inherit;
+    border-color: #dee2e6;
+  }
+}
+/*# sourceMappingURL=bootstrap.css.map */

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 0
vendor/bootstrap/scss/bootstrap.css.map


BIN
vendor/fontawesome-free/.DS_Store


+ 18 - 0
vendor/fontawesome-free/scss/brands.css

@@ -0,0 +1,18 @@
+/*!
+ * Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face {
+  font-family: 'Font Awesome 5 Brands';
+  font-style: normal;
+  font-weight: 400;
+  font-display: block;
+  src: url("../webfonts/fa-brands-400.eot");
+  src: url("../webfonts/fa-brands-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-brands-400.woff2") format("woff2"), url("../webfonts/fa-brands-400.woff") format("woff"), url("../webfonts/fa-brands-400.ttf") format("truetype"), url("../webfonts/fa-brands-400.svg#fontawesome") format("svg");
+}
+
+.fab {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+/*# sourceMappingURL=brands.css.map */

+ 10 - 0
vendor/fontawesome-free/scss/brands.css.map

@@ -0,0 +1,10 @@
+{
+    "version": 3,
+    "mappings": "AAAA;;;GAGG;AAGH,UAAU;EACR,WAAW,EAAE,uBAAuB;EACpC,UAAU,EAAE,MAAM;EAClB,WAAW,EAAE,GAAG;EAChB,YAAY,ECLS,KAAK;EDM1B,GAAG,EAAE,oCAAyC;EAC9C,GAAG,EAAE,2CAAgD,CAAC,2BAA2B,EACjF,sCAA2C,CAAC,eAAe,EAC3D,qCAA0C,CAAC,cAAc,EACzD,oCAAyC,CAAC,kBAAkB,EAC5D,gDAAqD,CAAC,aAAa;;;AAGrE,AAAA,IAAI,CAAC;EACH,WAAW,EAAE,uBAAuB;EACpC,WAAW,EAAE,GAAG;CACjB",
+    "sources": [
+        "brands.scss",
+        "_variables.scss"
+    ],
+    "names": [],
+    "file": "brands.css"
+}

+ 6091 - 0
vendor/fontawesome-free/scss/fontawesome.css

@@ -0,0 +1,6091 @@
+/*!
+ * Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+.fa,
+.fas,
+.far,
+.fal,
+.fad,
+.fab {
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-font-smoothing: antialiased;
+  display: inline-block;
+  font-style: normal;
+  font-variant: normal;
+  text-rendering: auto;
+  line-height: 1;
+}
+
+.fa-lg {
+  font-size: 1.33333em;
+  line-height: 0.75em;
+  vertical-align: -.0667em;
+}
+
+.fa-xs {
+  font-size: .75em;
+}
+
+.fa-sm {
+  font-size: .875em;
+}
+
+.fa-1x {
+  font-size: 1em;
+}
+
+.fa-2x {
+  font-size: 2em;
+}
+
+.fa-3x {
+  font-size: 3em;
+}
+
+.fa-4x {
+  font-size: 4em;
+}
+
+.fa-5x {
+  font-size: 5em;
+}
+
+.fa-6x {
+  font-size: 6em;
+}
+
+.fa-7x {
+  font-size: 7em;
+}
+
+.fa-8x {
+  font-size: 8em;
+}
+
+.fa-9x {
+  font-size: 9em;
+}
+
+.fa-10x {
+  font-size: 10em;
+}
+
+.fa-fw {
+  text-align: center;
+  width: 1.25em;
+}
+
+.fa-ul {
+  list-style-type: none;
+  margin-left: 2.5em;
+  padding-left: 0;
+}
+
+.fa-ul > li {
+  position: relative;
+}
+
+.fa-li {
+  left: -2em;
+  position: absolute;
+  text-align: center;
+  width: 2em;
+  line-height: inherit;
+}
+
+.fa-border {
+  border: solid 0.08em #eee;
+  border-radius: .1em;
+  padding: .2em .25em .15em;
+}
+
+.fa-pull-left {
+  float: left;
+}
+
+.fa-pull-right {
+  float: right;
+}
+
+.fa.fa-pull-left,
+.fas.fa-pull-left,
+.far.fa-pull-left,
+.fal.fa-pull-left,
+.fab.fa-pull-left {
+  margin-right: .3em;
+}
+
+.fa.fa-pull-right,
+.fas.fa-pull-right,
+.far.fa-pull-right,
+.fal.fa-pull-right,
+.fab.fa-pull-right {
+  margin-left: .3em;
+}
+
+.fa-spin {
+  -webkit-animation: fa-spin 2s infinite linear;
+          animation: fa-spin 2s infinite linear;
+}
+
+.fa-pulse {
+  -webkit-animation: fa-spin 1s infinite steps(8);
+          animation: fa-spin 1s infinite steps(8);
+}
+
+@-webkit-keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+            transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(360deg);
+            transform: rotate(360deg);
+  }
+}
+
+@keyframes fa-spin {
+  0% {
+    -webkit-transform: rotate(0deg);
+            transform: rotate(0deg);
+  }
+  100% {
+    -webkit-transform: rotate(360deg);
+            transform: rotate(360deg);
+  }
+}
+
+.fa-rotate-90 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";
+  -webkit-transform: rotate(90deg);
+          transform: rotate(90deg);
+}
+
+.fa-rotate-180 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";
+  -webkit-transform: rotate(180deg);
+          transform: rotate(180deg);
+}
+
+.fa-rotate-270 {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";
+  -webkit-transform: rotate(270deg);
+          transform: rotate(270deg);
+}
+
+.fa-flip-horizontal {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";
+  -webkit-transform: scale(-1, 1);
+          transform: scale(-1, 1);
+}
+
+.fa-flip-vertical {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
+  -webkit-transform: scale(1, -1);
+          transform: scale(1, -1);
+}
+
+.fa-flip-both, .fa-flip-horizontal.fa-flip-vertical {
+  -ms-filter: "progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";
+  -webkit-transform: scale(-1, -1);
+          transform: scale(-1, -1);
+}
+
+:root .fa-rotate-90,
+:root .fa-rotate-180,
+:root .fa-rotate-270,
+:root .fa-flip-horizontal,
+:root .fa-flip-vertical,
+:root .fa-flip-both {
+  -webkit-filter: none;
+          filter: none;
+}
+
+.fa-stack {
+  display: inline-block;
+  height: 2em;
+  line-height: 2em;
+  position: relative;
+  vertical-align: middle;
+  width: 2.5em;
+}
+
+.fa-stack-1x,
+.fa-stack-2x {
+  left: 0;
+  position: absolute;
+  text-align: center;
+  width: 100%;
+}
+
+.fa-stack-1x {
+  line-height: inherit;
+}
+
+.fa-stack-2x {
+  font-size: 2em;
+}
+
+.fa-inverse {
+  color: #fff;
+}
+
+/* Font Awesome uses the Unicode Private Use Area (PUA) to ensure screen
+readers do not read off random characters that represent icons */
+.fa-500px:before {
+  content: "\f26e";
+}
+
+.fa-accessible-icon:before {
+  content: "\f368";
+}
+
+.fa-accusoft:before {
+  content: "\f369";
+}
+
+.fa-acquisitions-incorporated:before {
+  content: "\f6af";
+}
+
+.fa-ad:before {
+  content: "\f641";
+}
+
+.fa-address-book:before {
+  content: "\f2b9";
+}
+
+.fa-address-card:before {
+  content: "\f2bb";
+}
+
+.fa-adjust:before {
+  content: "\f042";
+}
+
+.fa-adn:before {
+  content: "\f170";
+}
+
+.fa-adversal:before {
+  content: "\f36a";
+}
+
+.fa-affiliatetheme:before {
+  content: "\f36b";
+}
+
+.fa-air-freshener:before {
+  content: "\f5d0";
+}
+
+.fa-airbnb:before {
+  content: "\f834";
+}
+
+.fa-algolia:before {
+  content: "\f36c";
+}
+
+.fa-align-center:before {
+  content: "\f037";
+}
+
+.fa-align-justify:before {
+  content: "\f039";
+}
+
+.fa-align-left:before {
+  content: "\f036";
+}
+
+.fa-align-right:before {
+  content: "\f038";
+}
+
+.fa-alipay:before {
+  content: "\f642";
+}
+
+.fa-allergies:before {
+  content: "\f461";
+}
+
+.fa-amazon:before {
+  content: "\f270";
+}
+
+.fa-amazon-pay:before {
+  content: "\f42c";
+}
+
+.fa-ambulance:before {
+  content: "\f0f9";
+}
+
+.fa-american-sign-language-interpreting:before {
+  content: "\f2a3";
+}
+
+.fa-amilia:before {
+  content: "\f36d";
+}
+
+.fa-anchor:before {
+  content: "\f13d";
+}
+
+.fa-android:before {
+  content: "\f17b";
+}
+
+.fa-angellist:before {
+  content: "\f209";
+}
+
+.fa-angle-double-down:before {
+  content: "\f103";
+}
+
+.fa-angle-double-left:before {
+  content: "\f100";
+}
+
+.fa-angle-double-right:before {
+  content: "\f101";
+}
+
+.fa-angle-double-up:before {
+  content: "\f102";
+}
+
+.fa-angle-down:before {
+  content: "\f107";
+}
+
+.fa-angle-left:before {
+  content: "\f104";
+}
+
+.fa-angle-right:before {
+  content: "\f105";
+}
+
+.fa-angle-up:before {
+  content: "\f106";
+}
+
+.fa-angry:before {
+  content: "\f556";
+}
+
+.fa-angrycreative:before {
+  content: "\f36e";
+}
+
+.fa-angular:before {
+  content: "\f420";
+}
+
+.fa-ankh:before {
+  content: "\f644";
+}
+
+.fa-app-store:before {
+  content: "\f36f";
+}
+
+.fa-app-store-ios:before {
+  content: "\f370";
+}
+
+.fa-apper:before {
+  content: "\f371";
+}
+
+.fa-apple:before {
+  content: "\f179";
+}
+
+.fa-apple-alt:before {
+  content: "\f5d1";
+}
+
+.fa-apple-pay:before {
+  content: "\f415";
+}
+
+.fa-archive:before {
+  content: "\f187";
+}
+
+.fa-archway:before {
+  content: "\f557";
+}
+
+.fa-arrow-alt-circle-down:before {
+  content: "\f358";
+}
+
+.fa-arrow-alt-circle-left:before {
+  content: "\f359";
+}
+
+.fa-arrow-alt-circle-right:before {
+  content: "\f35a";
+}
+
+.fa-arrow-alt-circle-up:before {
+  content: "\f35b";
+}
+
+.fa-arrow-circle-down:before {
+  content: "\f0ab";
+}
+
+.fa-arrow-circle-left:before {
+  content: "\f0a8";
+}
+
+.fa-arrow-circle-right:before {
+  content: "\f0a9";
+}
+
+.fa-arrow-circle-up:before {
+  content: "\f0aa";
+}
+
+.fa-arrow-down:before {
+  content: "\f063";
+}
+
+.fa-arrow-left:before {
+  content: "\f060";
+}
+
+.fa-arrow-right:before {
+  content: "\f061";
+}
+
+.fa-arrow-up:before {
+  content: "\f062";
+}
+
+.fa-arrows-alt:before {
+  content: "\f0b2";
+}
+
+.fa-arrows-alt-h:before {
+  content: "\f337";
+}
+
+.fa-arrows-alt-v:before {
+  content: "\f338";
+}
+
+.fa-artstation:before {
+  content: "\f77a";
+}
+
+.fa-assistive-listening-systems:before {
+  content: "\f2a2";
+}
+
+.fa-asterisk:before {
+  content: "\f069";
+}
+
+.fa-asymmetrik:before {
+  content: "\f372";
+}
+
+.fa-at:before {
+  content: "\f1fa";
+}
+
+.fa-atlas:before {
+  content: "\f558";
+}
+
+.fa-atlassian:before {
+  content: "\f77b";
+}
+
+.fa-atom:before {
+  content: "\f5d2";
+}
+
+.fa-audible:before {
+  content: "\f373";
+}
+
+.fa-audio-description:before {
+  content: "\f29e";
+}
+
+.fa-autoprefixer:before {
+  content: "\f41c";
+}
+
+.fa-avianex:before {
+  content: "\f374";
+}
+
+.fa-aviato:before {
+  content: "\f421";
+}
+
+.fa-award:before {
+  content: "\f559";
+}
+
+.fa-aws:before {
+  content: "\f375";
+}
+
+.fa-baby:before {
+  content: "\f77c";
+}
+
+.fa-baby-carriage:before {
+  content: "\f77d";
+}
+
+.fa-backspace:before {
+  content: "\f55a";
+}
+
+.fa-backward:before {
+  content: "\f04a";
+}
+
+.fa-bacon:before {
+  content: "\f7e5";
+}
+
+.fa-bacteria:before {
+  content: "\e059";
+}
+
+.fa-bacterium:before {
+  content: "\e05a";
+}
+
+.fa-bahai:before {
+  content: "\f666";
+}
+
+.fa-balance-scale:before {
+  content: "\f24e";
+}
+
+.fa-balance-scale-left:before {
+  content: "\f515";
+}
+
+.fa-balance-scale-right:before {
+  content: "\f516";
+}
+
+.fa-ban:before {
+  content: "\f05e";
+}
+
+.fa-band-aid:before {
+  content: "\f462";
+}
+
+.fa-bandcamp:before {
+  content: "\f2d5";
+}
+
+.fa-barcode:before {
+  content: "\f02a";
+}
+
+.fa-bars:before {
+  content: "\f0c9";
+}
+
+.fa-baseball-ball:before {
+  content: "\f433";
+}
+
+.fa-basketball-ball:before {
+  content: "\f434";
+}
+
+.fa-bath:before {
+  content: "\f2cd";
+}
+
+.fa-battery-empty:before {
+  content: "\f244";
+}
+
+.fa-battery-full:before {
+  content: "\f240";
+}
+
+.fa-battery-half:before {
+  content: "\f242";
+}
+
+.fa-battery-quarter:before {
+  content: "\f243";
+}
+
+.fa-battery-three-quarters:before {
+  content: "\f241";
+}
+
+.fa-battle-net:before {
+  content: "\f835";
+}
+
+.fa-bed:before {
+  content: "\f236";
+}
+
+.fa-beer:before {
+  content: "\f0fc";
+}
+
+.fa-behance:before {
+  content: "\f1b4";
+}
+
+.fa-behance-square:before {
+  content: "\f1b5";
+}
+
+.fa-bell:before {
+  content: "\f0f3";
+}
+
+.fa-bell-slash:before {
+  content: "\f1f6";
+}
+
+.fa-bezier-curve:before {
+  content: "\f55b";
+}
+
+.fa-bible:before {
+  content: "\f647";
+}
+
+.fa-bicycle:before {
+  content: "\f206";
+}
+
+.fa-biking:before {
+  content: "\f84a";
+}
+
+.fa-bimobject:before {
+  content: "\f378";
+}
+
+.fa-binoculars:before {
+  content: "\f1e5";
+}
+
+.fa-biohazard:before {
+  content: "\f780";
+}
+
+.fa-birthday-cake:before {
+  content: "\f1fd";
+}
+
+.fa-bitbucket:before {
+  content: "\f171";
+}
+
+.fa-bitcoin:before {
+  content: "\f379";
+}
+
+.fa-bity:before {
+  content: "\f37a";
+}
+
+.fa-black-tie:before {
+  content: "\f27e";
+}
+
+.fa-blackberry:before {
+  content: "\f37b";
+}
+
+.fa-blender:before {
+  content: "\f517";
+}
+
+.fa-blender-phone:before {
+  content: "\f6b6";
+}
+
+.fa-blind:before {
+  content: "\f29d";
+}
+
+.fa-blog:before {
+  content: "\f781";
+}
+
+.fa-blogger:before {
+  content: "\f37c";
+}
+
+.fa-blogger-b:before {
+  content: "\f37d";
+}
+
+.fa-bluetooth:before {
+  content: "\f293";
+}
+
+.fa-bluetooth-b:before {
+  content: "\f294";
+}
+
+.fa-bold:before {
+  content: "\f032";
+}
+
+.fa-bolt:before {
+  content: "\f0e7";
+}
+
+.fa-bomb:before {
+  content: "\f1e2";
+}
+
+.fa-bone:before {
+  content: "\f5d7";
+}
+
+.fa-bong:before {
+  content: "\f55c";
+}
+
+.fa-book:before {
+  content: "\f02d";
+}
+
+.fa-book-dead:before {
+  content: "\f6b7";
+}
+
+.fa-book-medical:before {
+  content: "\f7e6";
+}
+
+.fa-book-open:before {
+  content: "\f518";
+}
+
+.fa-book-reader:before {
+  content: "\f5da";
+}
+
+.fa-bookmark:before {
+  content: "\f02e";
+}
+
+.fa-bootstrap:before {
+  content: "\f836";
+}
+
+.fa-border-all:before {
+  content: "\f84c";
+}
+
+.fa-border-none:before {
+  content: "\f850";
+}
+
+.fa-border-style:before {
+  content: "\f853";
+}
+
+.fa-bowling-ball:before {
+  content: "\f436";
+}
+
+.fa-box:before {
+  content: "\f466";
+}
+
+.fa-box-open:before {
+  content: "\f49e";
+}
+
+.fa-box-tissue:before {
+  content: "\e05b";
+}
+
+.fa-boxes:before {
+  content: "\f468";
+}
+
+.fa-braille:before {
+  content: "\f2a1";
+}
+
+.fa-brain:before {
+  content: "\f5dc";
+}
+
+.fa-bread-slice:before {
+  content: "\f7ec";
+}
+
+.fa-briefcase:before {
+  content: "\f0b1";
+}
+
+.fa-briefcase-medical:before {
+  content: "\f469";
+}
+
+.fa-broadcast-tower:before {
+  content: "\f519";
+}
+
+.fa-broom:before {
+  content: "\f51a";
+}
+
+.fa-brush:before {
+  content: "\f55d";
+}
+
+.fa-btc:before {
+  content: "\f15a";
+}
+
+.fa-buffer:before {
+  content: "\f837";
+}
+
+.fa-bug:before {
+  content: "\f188";
+}
+
+.fa-building:before {
+  content: "\f1ad";
+}
+
+.fa-bullhorn:before {
+  content: "\f0a1";
+}
+
+.fa-bullseye:before {
+  content: "\f140";
+}
+
+.fa-burn:before {
+  content: "\f46a";
+}
+
+.fa-buromobelexperte:before {
+  content: "\f37f";
+}
+
+.fa-bus:before {
+  content: "\f207";
+}
+
+.fa-bus-alt:before {
+  content: "\f55e";
+}
+
+.fa-business-time:before {
+  content: "\f64a";
+}
+
+.fa-buy-n-large:before {
+  content: "\f8a6";
+}
+
+.fa-buysellads:before {
+  content: "\f20d";
+}
+
+.fa-calculator:before {
+  content: "\f1ec";
+}
+
+.fa-calendar:before {
+  content: "\f133";
+}
+
+.fa-calendar-alt:before {
+  content: "\f073";
+}
+
+.fa-calendar-check:before {
+  content: "\f274";
+}
+
+.fa-calendar-day:before {
+  content: "\f783";
+}
+
+.fa-calendar-minus:before {
+  content: "\f272";
+}
+
+.fa-calendar-plus:before {
+  content: "\f271";
+}
+
+.fa-calendar-times:before {
+  content: "\f273";
+}
+
+.fa-calendar-week:before {
+  content: "\f784";
+}
+
+.fa-camera:before {
+  content: "\f030";
+}
+
+.fa-camera-retro:before {
+  content: "\f083";
+}
+
+.fa-campground:before {
+  content: "\f6bb";
+}
+
+.fa-canadian-maple-leaf:before {
+  content: "\f785";
+}
+
+.fa-candy-cane:before {
+  content: "\f786";
+}
+
+.fa-cannabis:before {
+  content: "\f55f";
+}
+
+.fa-capsules:before {
+  content: "\f46b";
+}
+
+.fa-car:before {
+  content: "\f1b9";
+}
+
+.fa-car-alt:before {
+  content: "\f5de";
+}
+
+.fa-car-battery:before {
+  content: "\f5df";
+}
+
+.fa-car-crash:before {
+  content: "\f5e1";
+}
+
+.fa-car-side:before {
+  content: "\f5e4";
+}
+
+.fa-caravan:before {
+  content: "\f8ff";
+}
+
+.fa-caret-down:before {
+  content: "\f0d7";
+}
+
+.fa-caret-left:before {
+  content: "\f0d9";
+}
+
+.fa-caret-right:before {
+  content: "\f0da";
+}
+
+.fa-caret-square-down:before {
+  content: "\f150";
+}
+
+.fa-caret-square-left:before {
+  content: "\f191";
+}
+
+.fa-caret-square-right:before {
+  content: "\f152";
+}
+
+.fa-caret-square-up:before {
+  content: "\f151";
+}
+
+.fa-caret-up:before {
+  content: "\f0d8";
+}
+
+.fa-carrot:before {
+  content: "\f787";
+}
+
+.fa-cart-arrow-down:before {
+  content: "\f218";
+}
+
+.fa-cart-plus:before {
+  content: "\f217";
+}
+
+.fa-cash-register:before {
+  content: "\f788";
+}
+
+.fa-cat:before {
+  content: "\f6be";
+}
+
+.fa-cc-amazon-pay:before {
+  content: "\f42d";
+}
+
+.fa-cc-amex:before {
+  content: "\f1f3";
+}
+
+.fa-cc-apple-pay:before {
+  content: "\f416";
+}
+
+.fa-cc-diners-club:before {
+  content: "\f24c";
+}
+
+.fa-cc-discover:before {
+  content: "\f1f2";
+}
+
+.fa-cc-jcb:before {
+  content: "\f24b";
+}
+
+.fa-cc-mastercard:before {
+  content: "\f1f1";
+}
+
+.fa-cc-paypal:before {
+  content: "\f1f4";
+}
+
+.fa-cc-stripe:before {
+  content: "\f1f5";
+}
+
+.fa-cc-visa:before {
+  content: "\f1f0";
+}
+
+.fa-centercode:before {
+  content: "\f380";
+}
+
+.fa-centos:before {
+  content: "\f789";
+}
+
+.fa-certificate:before {
+  content: "\f0a3";
+}
+
+.fa-chair:before {
+  content: "\f6c0";
+}
+
+.fa-chalkboard:before {
+  content: "\f51b";
+}
+
+.fa-chalkboard-teacher:before {
+  content: "\f51c";
+}
+
+.fa-charging-station:before {
+  content: "\f5e7";
+}
+
+.fa-chart-area:before {
+  content: "\f1fe";
+}
+
+.fa-chart-bar:before {
+  content: "\f080";
+}
+
+.fa-chart-line:before {
+  content: "\f201";
+}
+
+.fa-chart-pie:before {
+  content: "\f200";
+}
+
+.fa-check:before {
+  content: "\f00c";
+}
+
+.fa-check-circle:before {
+  content: "\f058";
+}
+
+.fa-check-double:before {
+  content: "\f560";
+}
+
+.fa-check-square:before {
+  content: "\f14a";
+}
+
+.fa-cheese:before {
+  content: "\f7ef";
+}
+
+.fa-chess:before {
+  content: "\f439";
+}
+
+.fa-chess-bishop:before {
+  content: "\f43a";
+}
+
+.fa-chess-board:before {
+  content: "\f43c";
+}
+
+.fa-chess-king:before {
+  content: "\f43f";
+}
+
+.fa-chess-knight:before {
+  content: "\f441";
+}
+
+.fa-chess-pawn:before {
+  content: "\f443";
+}
+
+.fa-chess-queen:before {
+  content: "\f445";
+}
+
+.fa-chess-rook:before {
+  content: "\f447";
+}
+
+.fa-chevron-circle-down:before {
+  content: "\f13a";
+}
+
+.fa-chevron-circle-left:before {
+  content: "\f137";
+}
+
+.fa-chevron-circle-right:before {
+  content: "\f138";
+}
+
+.fa-chevron-circle-up:before {
+  content: "\f139";
+}
+
+.fa-chevron-down:before {
+  content: "\f078";
+}
+
+.fa-chevron-left:before {
+  content: "\f053";
+}
+
+.fa-chevron-right:before {
+  content: "\f054";
+}
+
+.fa-chevron-up:before {
+  content: "\f077";
+}
+
+.fa-child:before {
+  content: "\f1ae";
+}
+
+.fa-chrome:before {
+  content: "\f268";
+}
+
+.fa-chromecast:before {
+  content: "\f838";
+}
+
+.fa-church:before {
+  content: "\f51d";
+}
+
+.fa-circle:before {
+  content: "\f111";
+}
+
+.fa-circle-notch:before {
+  content: "\f1ce";
+}
+
+.fa-city:before {
+  content: "\f64f";
+}
+
+.fa-clinic-medical:before {
+  content: "\f7f2";
+}
+
+.fa-clipboard:before {
+  content: "\f328";
+}
+
+.fa-clipboard-check:before {
+  content: "\f46c";
+}
+
+.fa-clipboard-list:before {
+  content: "\f46d";
+}
+
+.fa-clock:before {
+  content: "\f017";
+}
+
+.fa-clone:before {
+  content: "\f24d";
+}
+
+.fa-closed-captioning:before {
+  content: "\f20a";
+}
+
+.fa-cloud:before {
+  content: "\f0c2";
+}
+
+.fa-cloud-download-alt:before {
+  content: "\f381";
+}
+
+.fa-cloud-meatball:before {
+  content: "\f73b";
+}
+
+.fa-cloud-moon:before {
+  content: "\f6c3";
+}
+
+.fa-cloud-moon-rain:before {
+  content: "\f73c";
+}
+
+.fa-cloud-rain:before {
+  content: "\f73d";
+}
+
+.fa-cloud-showers-heavy:before {
+  content: "\f740";
+}
+
+.fa-cloud-sun:before {
+  content: "\f6c4";
+}
+
+.fa-cloud-sun-rain:before {
+  content: "\f743";
+}
+
+.fa-cloud-upload-alt:before {
+  content: "\f382";
+}
+
+.fa-cloudflare:before {
+  content: "\e07d";
+}
+
+.fa-cloudscale:before {
+  content: "\f383";
+}
+
+.fa-cloudsmith:before {
+  content: "\f384";
+}
+
+.fa-cloudversify:before {
+  content: "\f385";
+}
+
+.fa-cocktail:before {
+  content: "\f561";
+}
+
+.fa-code:before {
+  content: "\f121";
+}
+
+.fa-code-branch:before {
+  content: "\f126";
+}
+
+.fa-codepen:before {
+  content: "\f1cb";
+}
+
+.fa-codiepie:before {
+  content: "\f284";
+}
+
+.fa-coffee:before {
+  content: "\f0f4";
+}
+
+.fa-cog:before {
+  content: "\f013";
+}
+
+.fa-cogs:before {
+  content: "\f085";
+}
+
+.fa-coins:before {
+  content: "\f51e";
+}
+
+.fa-columns:before {
+  content: "\f0db";
+}
+
+.fa-comment:before {
+  content: "\f075";
+}
+
+.fa-comment-alt:before {
+  content: "\f27a";
+}
+
+.fa-comment-dollar:before {
+  content: "\f651";
+}
+
+.fa-comment-dots:before {
+  content: "\f4ad";
+}
+
+.fa-comment-medical:before {
+  content: "\f7f5";
+}
+
+.fa-comment-slash:before {
+  content: "\f4b3";
+}
+
+.fa-comments:before {
+  content: "\f086";
+}
+
+.fa-comments-dollar:before {
+  content: "\f653";
+}
+
+.fa-compact-disc:before {
+  content: "\f51f";
+}
+
+.fa-compass:before {
+  content: "\f14e";
+}
+
+.fa-compress:before {
+  content: "\f066";
+}
+
+.fa-compress-alt:before {
+  content: "\f422";
+}
+
+.fa-compress-arrows-alt:before {
+  content: "\f78c";
+}
+
+.fa-concierge-bell:before {
+  content: "\f562";
+}
+
+.fa-confluence:before {
+  content: "\f78d";
+}
+
+.fa-connectdevelop:before {
+  content: "\f20e";
+}
+
+.fa-contao:before {
+  content: "\f26d";
+}
+
+.fa-cookie:before {
+  content: "\f563";
+}
+
+.fa-cookie-bite:before {
+  content: "\f564";
+}
+
+.fa-copy:before {
+  content: "\f0c5";
+}
+
+.fa-copyright:before {
+  content: "\f1f9";
+}
+
+.fa-cotton-bureau:before {
+  content: "\f89e";
+}
+
+.fa-couch:before {
+  content: "\f4b8";
+}
+
+.fa-cpanel:before {
+  content: "\f388";
+}
+
+.fa-creative-commons:before {
+  content: "\f25e";
+}
+
+.fa-creative-commons-by:before {
+  content: "\f4e7";
+}
+
+.fa-creative-commons-nc:before {
+  content: "\f4e8";
+}
+
+.fa-creative-commons-nc-eu:before {
+  content: "\f4e9";
+}
+
+.fa-creative-commons-nc-jp:before {
+  content: "\f4ea";
+}
+
+.fa-creative-commons-nd:before {
+  content: "\f4eb";
+}
+
+.fa-creative-commons-pd:before {
+  content: "\f4ec";
+}
+
+.fa-creative-commons-pd-alt:before {
+  content: "\f4ed";
+}
+
+.fa-creative-commons-remix:before {
+  content: "\f4ee";
+}
+
+.fa-creative-commons-sa:before {
+  content: "\f4ef";
+}
+
+.fa-creative-commons-sampling:before {
+  content: "\f4f0";
+}
+
+.fa-creative-commons-sampling-plus:before {
+  content: "\f4f1";
+}
+
+.fa-creative-commons-share:before {
+  content: "\f4f2";
+}
+
+.fa-creative-commons-zero:before {
+  content: "\f4f3";
+}
+
+.fa-credit-card:before {
+  content: "\f09d";
+}
+
+.fa-critical-role:before {
+  content: "\f6c9";
+}
+
+.fa-crop:before {
+  content: "\f125";
+}
+
+.fa-crop-alt:before {
+  content: "\f565";
+}
+
+.fa-cross:before {
+  content: "\f654";
+}
+
+.fa-crosshairs:before {
+  content: "\f05b";
+}
+
+.fa-crow:before {
+  content: "\f520";
+}
+
+.fa-crown:before {
+  content: "\f521";
+}
+
+.fa-crutch:before {
+  content: "\f7f7";
+}
+
+.fa-css3:before {
+  content: "\f13c";
+}
+
+.fa-css3-alt:before {
+  content: "\f38b";
+}
+
+.fa-cube:before {
+  content: "\f1b2";
+}
+
+.fa-cubes:before {
+  content: "\f1b3";
+}
+
+.fa-cut:before {
+  content: "\f0c4";
+}
+
+.fa-cuttlefish:before {
+  content: "\f38c";
+}
+
+.fa-d-and-d:before {
+  content: "\f38d";
+}
+
+.fa-d-and-d-beyond:before {
+  content: "\f6ca";
+}
+
+.fa-dailymotion:before {
+  content: "\e052";
+}
+
+.fa-dashcube:before {
+  content: "\f210";
+}
+
+.fa-database:before {
+  content: "\f1c0";
+}
+
+.fa-deaf:before {
+  content: "\f2a4";
+}
+
+.fa-deezer:before {
+  content: "\e077";
+}
+
+.fa-delicious:before {
+  content: "\f1a5";
+}
+
+.fa-democrat:before {
+  content: "\f747";
+}
+
+.fa-deploydog:before {
+  content: "\f38e";
+}
+
+.fa-deskpro:before {
+  content: "\f38f";
+}
+
+.fa-desktop:before {
+  content: "\f108";
+}
+
+.fa-dev:before {
+  content: "\f6cc";
+}
+
+.fa-deviantart:before {
+  content: "\f1bd";
+}
+
+.fa-dharmachakra:before {
+  content: "\f655";
+}
+
+.fa-dhl:before {
+  content: "\f790";
+}
+
+.fa-diagnoses:before {
+  content: "\f470";
+}
+
+.fa-diaspora:before {
+  content: "\f791";
+}
+
+.fa-dice:before {
+  content: "\f522";
+}
+
+.fa-dice-d20:before {
+  content: "\f6cf";
+}
+
+.fa-dice-d6:before {
+  content: "\f6d1";
+}
+
+.fa-dice-five:before {
+  content: "\f523";
+}
+
+.fa-dice-four:before {
+  content: "\f524";
+}
+
+.fa-dice-one:before {
+  content: "\f525";
+}
+
+.fa-dice-six:before {
+  content: "\f526";
+}
+
+.fa-dice-three:before {
+  content: "\f527";
+}
+
+.fa-dice-two:before {
+  content: "\f528";
+}
+
+.fa-digg:before {
+  content: "\f1a6";
+}
+
+.fa-digital-ocean:before {
+  content: "\f391";
+}
+
+.fa-digital-tachograph:before {
+  content: "\f566";
+}
+
+.fa-directions:before {
+  content: "\f5eb";
+}
+
+.fa-discord:before {
+  content: "\f392";
+}
+
+.fa-discourse:before {
+  content: "\f393";
+}
+
+.fa-disease:before {
+  content: "\f7fa";
+}
+
+.fa-divide:before {
+  content: "\f529";
+}
+
+.fa-dizzy:before {
+  content: "\f567";
+}
+
+.fa-dna:before {
+  content: "\f471";
+}
+
+.fa-dochub:before {
+  content: "\f394";
+}
+
+.fa-docker:before {
+  content: "\f395";
+}
+
+.fa-dog:before {
+  content: "\f6d3";
+}
+
+.fa-dollar-sign:before {
+  content: "\f155";
+}
+
+.fa-dolly:before {
+  content: "\f472";
+}
+
+.fa-dolly-flatbed:before {
+  content: "\f474";
+}
+
+.fa-donate:before {
+  content: "\f4b9";
+}
+
+.fa-door-closed:before {
+  content: "\f52a";
+}
+
+.fa-door-open:before {
+  content: "\f52b";
+}
+
+.fa-dot-circle:before {
+  content: "\f192";
+}
+
+.fa-dove:before {
+  content: "\f4ba";
+}
+
+.fa-download:before {
+  content: "\f019";
+}
+
+.fa-draft2digital:before {
+  content: "\f396";
+}
+
+.fa-drafting-compass:before {
+  content: "\f568";
+}
+
+.fa-dragon:before {
+  content: "\f6d5";
+}
+
+.fa-draw-polygon:before {
+  content: "\f5ee";
+}
+
+.fa-dribbble:before {
+  content: "\f17d";
+}
+
+.fa-dribbble-square:before {
+  content: "\f397";
+}
+
+.fa-dropbox:before {
+  content: "\f16b";
+}
+
+.fa-drum:before {
+  content: "\f569";
+}
+
+.fa-drum-steelpan:before {
+  content: "\f56a";
+}
+
+.fa-drumstick-bite:before {
+  content: "\f6d7";
+}
+
+.fa-drupal:before {
+  content: "\f1a9";
+}
+
+.fa-dumbbell:before {
+  content: "\f44b";
+}
+
+.fa-dumpster:before {
+  content: "\f793";
+}
+
+.fa-dumpster-fire:before {
+  content: "\f794";
+}
+
+.fa-dungeon:before {
+  content: "\f6d9";
+}
+
+.fa-dyalog:before {
+  content: "\f399";
+}
+
+.fa-earlybirds:before {
+  content: "\f39a";
+}
+
+.fa-ebay:before {
+  content: "\f4f4";
+}
+
+.fa-edge:before {
+  content: "\f282";
+}
+
+.fa-edge-legacy:before {
+  content: "\e078";
+}
+
+.fa-edit:before {
+  content: "\f044";
+}
+
+.fa-egg:before {
+  content: "\f7fb";
+}
+
+.fa-eject:before {
+  content: "\f052";
+}
+
+.fa-elementor:before {
+  content: "\f430";
+}
+
+.fa-ellipsis-h:before {
+  content: "\f141";
+}
+
+.fa-ellipsis-v:before {
+  content: "\f142";
+}
+
+.fa-ello:before {
+  content: "\f5f1";
+}
+
+.fa-ember:before {
+  content: "\f423";
+}
+
+.fa-empire:before {
+  content: "\f1d1";
+}
+
+.fa-envelope:before {
+  content: "\f0e0";
+}
+
+.fa-envelope-open:before {
+  content: "\f2b6";
+}
+
+.fa-envelope-open-text:before {
+  content: "\f658";
+}
+
+.fa-envelope-square:before {
+  content: "\f199";
+}
+
+.fa-envira:before {
+  content: "\f299";
+}
+
+.fa-equals:before {
+  content: "\f52c";
+}
+
+.fa-eraser:before {
+  content: "\f12d";
+}
+
+.fa-erlang:before {
+  content: "\f39d";
+}
+
+.fa-ethereum:before {
+  content: "\f42e";
+}
+
+.fa-ethernet:before {
+  content: "\f796";
+}
+
+.fa-etsy:before {
+  content: "\f2d7";
+}
+
+.fa-euro-sign:before {
+  content: "\f153";
+}
+
+.fa-evernote:before {
+  content: "\f839";
+}
+
+.fa-exchange-alt:before {
+  content: "\f362";
+}
+
+.fa-exclamation:before {
+  content: "\f12a";
+}
+
+.fa-exclamation-circle:before {
+  content: "\f06a";
+}
+
+.fa-exclamation-triangle:before {
+  content: "\f071";
+}
+
+.fa-expand:before {
+  content: "\f065";
+}
+
+.fa-expand-alt:before {
+  content: "\f424";
+}
+
+.fa-expand-arrows-alt:before {
+  content: "\f31e";
+}
+
+.fa-expeditedssl:before {
+  content: "\f23e";
+}
+
+.fa-external-link-alt:before {
+  content: "\f35d";
+}
+
+.fa-external-link-square-alt:before {
+  content: "\f360";
+}
+
+.fa-eye:before {
+  content: "\f06e";
+}
+
+.fa-eye-dropper:before {
+  content: "\f1fb";
+}
+
+.fa-eye-slash:before {
+  content: "\f070";
+}
+
+.fa-facebook:before {
+  content: "\f09a";
+}
+
+.fa-facebook-f:before {
+  content: "\f39e";
+}
+
+.fa-facebook-messenger:before {
+  content: "\f39f";
+}
+
+.fa-facebook-square:before {
+  content: "\f082";
+}
+
+.fa-fan:before {
+  content: "\f863";
+}
+
+.fa-fantasy-flight-games:before {
+  content: "\f6dc";
+}
+
+.fa-fast-backward:before {
+  content: "\f049";
+}
+
+.fa-fast-forward:before {
+  content: "\f050";
+}
+
+.fa-faucet:before {
+  content: "\e005";
+}
+
+.fa-fax:before {
+  content: "\f1ac";
+}
+
+.fa-feather:before {
+  content: "\f52d";
+}
+
+.fa-feather-alt:before {
+  content: "\f56b";
+}
+
+.fa-fedex:before {
+  content: "\f797";
+}
+
+.fa-fedora:before {
+  content: "\f798";
+}
+
+.fa-female:before {
+  content: "\f182";
+}
+
+.fa-fighter-jet:before {
+  content: "\f0fb";
+}
+
+.fa-figma:before {
+  content: "\f799";
+}
+
+.fa-file:before {
+  content: "\f15b";
+}
+
+.fa-file-alt:before {
+  content: "\f15c";
+}
+
+.fa-file-archive:before {
+  content: "\f1c6";
+}
+
+.fa-file-audio:before {
+  content: "\f1c7";
+}
+
+.fa-file-code:before {
+  content: "\f1c9";
+}
+
+.fa-file-contract:before {
+  content: "\f56c";
+}
+
+.fa-file-csv:before {
+  content: "\f6dd";
+}
+
+.fa-file-download:before {
+  content: "\f56d";
+}
+
+.fa-file-excel:before {
+  content: "\f1c3";
+}
+
+.fa-file-export:before {
+  content: "\f56e";
+}
+
+.fa-file-image:before {
+  content: "\f1c5";
+}
+
+.fa-file-import:before {
+  content: "\f56f";
+}
+
+.fa-file-invoice:before {
+  content: "\f570";
+}
+
+.fa-file-invoice-dollar:before {
+  content: "\f571";
+}
+
+.fa-file-medical:before {
+  content: "\f477";
+}
+
+.fa-file-medical-alt:before {
+  content: "\f478";
+}
+
+.fa-file-pdf:before {
+  content: "\f1c1";
+}
+
+.fa-file-powerpoint:before {
+  content: "\f1c4";
+}
+
+.fa-file-prescription:before {
+  content: "\f572";
+}
+
+.fa-file-signature:before {
+  content: "\f573";
+}
+
+.fa-file-upload:before {
+  content: "\f574";
+}
+
+.fa-file-video:before {
+  content: "\f1c8";
+}
+
+.fa-file-word:before {
+  content: "\f1c2";
+}
+
+.fa-fill:before {
+  content: "\f575";
+}
+
+.fa-fill-drip:before {
+  content: "\f576";
+}
+
+.fa-film:before {
+  content: "\f008";
+}
+
+.fa-filter:before {
+  content: "\f0b0";
+}
+
+.fa-fingerprint:before {
+  content: "\f577";
+}
+
+.fa-fire:before {
+  content: "\f06d";
+}
+
+.fa-fire-alt:before {
+  content: "\f7e4";
+}
+
+.fa-fire-extinguisher:before {
+  content: "\f134";
+}
+
+.fa-firefox:before {
+  content: "\f269";
+}
+
+.fa-firefox-browser:before {
+  content: "\e007";
+}
+
+.fa-first-aid:before {
+  content: "\f479";
+}
+
+.fa-first-order:before {
+  content: "\f2b0";
+}
+
+.fa-first-order-alt:before {
+  content: "\f50a";
+}
+
+.fa-firstdraft:before {
+  content: "\f3a1";
+}
+
+.fa-fish:before {
+  content: "\f578";
+}
+
+.fa-fist-raised:before {
+  content: "\f6de";
+}
+
+.fa-flag:before {
+  content: "\f024";
+}
+
+.fa-flag-checkered:before {
+  content: "\f11e";
+}
+
+.fa-flag-usa:before {
+  content: "\f74d";
+}
+
+.fa-flask:before {
+  content: "\f0c3";
+}
+
+.fa-flickr:before {
+  content: "\f16e";
+}
+
+.fa-flipboard:before {
+  content: "\f44d";
+}
+
+.fa-flushed:before {
+  content: "\f579";
+}
+
+.fa-fly:before {
+  content: "\f417";
+}
+
+.fa-folder:before {
+  content: "\f07b";
+}
+
+.fa-folder-minus:before {
+  content: "\f65d";
+}
+
+.fa-folder-open:before {
+  content: "\f07c";
+}
+
+.fa-folder-plus:before {
+  content: "\f65e";
+}
+
+.fa-font:before {
+  content: "\f031";
+}
+
+.fa-font-awesome:before {
+  content: "\f2b4";
+}
+
+.fa-font-awesome-alt:before {
+  content: "\f35c";
+}
+
+.fa-font-awesome-flag:before {
+  content: "\f425";
+}
+
+.fa-font-awesome-logo-full:before {
+  content: "\f4e6";
+}
+
+.fa-fonticons:before {
+  content: "\f280";
+}
+
+.fa-fonticons-fi:before {
+  content: "\f3a2";
+}
+
+.fa-football-ball:before {
+  content: "\f44e";
+}
+
+.fa-fort-awesome:before {
+  content: "\f286";
+}
+
+.fa-fort-awesome-alt:before {
+  content: "\f3a3";
+}
+
+.fa-forumbee:before {
+  content: "\f211";
+}
+
+.fa-forward:before {
+  content: "\f04e";
+}
+
+.fa-foursquare:before {
+  content: "\f180";
+}
+
+.fa-free-code-camp:before {
+  content: "\f2c5";
+}
+
+.fa-freebsd:before {
+  content: "\f3a4";
+}
+
+.fa-frog:before {
+  content: "\f52e";
+}
+
+.fa-frown:before {
+  content: "\f119";
+}
+
+.fa-frown-open:before {
+  content: "\f57a";
+}
+
+.fa-fulcrum:before {
+  content: "\f50b";
+}
+
+.fa-funnel-dollar:before {
+  content: "\f662";
+}
+
+.fa-futbol:before {
+  content: "\f1e3";
+}
+
+.fa-galactic-republic:before {
+  content: "\f50c";
+}
+
+.fa-galactic-senate:before {
+  content: "\f50d";
+}
+
+.fa-gamepad:before {
+  content: "\f11b";
+}
+
+.fa-gas-pump:before {
+  content: "\f52f";
+}
+
+.fa-gavel:before {
+  content: "\f0e3";
+}
+
+.fa-gem:before {
+  content: "\f3a5";
+}
+
+.fa-genderless:before {
+  content: "\f22d";
+}
+
+.fa-get-pocket:before {
+  content: "\f265";
+}
+
+.fa-gg:before {
+  content: "\f260";
+}
+
+.fa-gg-circle:before {
+  content: "\f261";
+}
+
+.fa-ghost:before {
+  content: "\f6e2";
+}
+
+.fa-gift:before {
+  content: "\f06b";
+}
+
+.fa-gifts:before {
+  content: "\f79c";
+}
+
+.fa-git:before {
+  content: "\f1d3";
+}
+
+.fa-git-alt:before {
+  content: "\f841";
+}
+
+.fa-git-square:before {
+  content: "\f1d2";
+}
+
+.fa-github:before {
+  content: "\f09b";
+}
+
+.fa-github-alt:before {
+  content: "\f113";
+}
+
+.fa-github-square:before {
+  content: "\f092";
+}
+
+.fa-gitkraken:before {
+  content: "\f3a6";
+}
+
+.fa-gitlab:before {
+  content: "\f296";
+}
+
+.fa-gitter:before {
+  content: "\f426";
+}
+
+.fa-glass-cheers:before {
+  content: "\f79f";
+}
+
+.fa-glass-martini:before {
+  content: "\f000";
+}
+
+.fa-glass-martini-alt:before {
+  content: "\f57b";
+}
+
+.fa-glass-whiskey:before {
+  content: "\f7a0";
+}
+
+.fa-glasses:before {
+  content: "\f530";
+}
+
+.fa-glide:before {
+  content: "\f2a5";
+}
+
+.fa-glide-g:before {
+  content: "\f2a6";
+}
+
+.fa-globe:before {
+  content: "\f0ac";
+}
+
+.fa-globe-africa:before {
+  content: "\f57c";
+}
+
+.fa-globe-americas:before {
+  content: "\f57d";
+}
+
+.fa-globe-asia:before {
+  content: "\f57e";
+}
+
+.fa-globe-europe:before {
+  content: "\f7a2";
+}
+
+.fa-gofore:before {
+  content: "\f3a7";
+}
+
+.fa-golf-ball:before {
+  content: "\f450";
+}
+
+.fa-goodreads:before {
+  content: "\f3a8";
+}
+
+.fa-goodreads-g:before {
+  content: "\f3a9";
+}
+
+.fa-google:before {
+  content: "\f1a0";
+}
+
+.fa-google-drive:before {
+  content: "\f3aa";
+}
+
+.fa-google-pay:before {
+  content: "\e079";
+}
+
+.fa-google-play:before {
+  content: "\f3ab";
+}
+
+.fa-google-plus:before {
+  content: "\f2b3";
+}
+
+.fa-google-plus-g:before {
+  content: "\f0d5";
+}
+
+.fa-google-plus-square:before {
+  content: "\f0d4";
+}
+
+.fa-google-wallet:before {
+  content: "\f1ee";
+}
+
+.fa-gopuram:before {
+  content: "\f664";
+}
+
+.fa-graduation-cap:before {
+  content: "\f19d";
+}
+
+.fa-gratipay:before {
+  content: "\f184";
+}
+
+.fa-grav:before {
+  content: "\f2d6";
+}
+
+.fa-greater-than:before {
+  content: "\f531";
+}
+
+.fa-greater-than-equal:before {
+  content: "\f532";
+}
+
+.fa-grimace:before {
+  content: "\f57f";
+}
+
+.fa-grin:before {
+  content: "\f580";
+}
+
+.fa-grin-alt:before {
+  content: "\f581";
+}
+
+.fa-grin-beam:before {
+  content: "\f582";
+}
+
+.fa-grin-beam-sweat:before {
+  content: "\f583";
+}
+
+.fa-grin-hearts:before {
+  content: "\f584";
+}
+
+.fa-grin-squint:before {
+  content: "\f585";
+}
+
+.fa-grin-squint-tears:before {
+  content: "\f586";
+}
+
+.fa-grin-stars:before {
+  content: "\f587";
+}
+
+.fa-grin-tears:before {
+  content: "\f588";
+}
+
+.fa-grin-tongue:before {
+  content: "\f589";
+}
+
+.fa-grin-tongue-squint:before {
+  content: "\f58a";
+}
+
+.fa-grin-tongue-wink:before {
+  content: "\f58b";
+}
+
+.fa-grin-wink:before {
+  content: "\f58c";
+}
+
+.fa-grip-horizontal:before {
+  content: "\f58d";
+}
+
+.fa-grip-lines:before {
+  content: "\f7a4";
+}
+
+.fa-grip-lines-vertical:before {
+  content: "\f7a5";
+}
+
+.fa-grip-vertical:before {
+  content: "\f58e";
+}
+
+.fa-gripfire:before {
+  content: "\f3ac";
+}
+
+.fa-grunt:before {
+  content: "\f3ad";
+}
+
+.fa-guilded:before {
+  content: "\e07e";
+}
+
+.fa-guitar:before {
+  content: "\f7a6";
+}
+
+.fa-gulp:before {
+  content: "\f3ae";
+}
+
+.fa-h-square:before {
+  content: "\f0fd";
+}
+
+.fa-hacker-news:before {
+  content: "\f1d4";
+}
+
+.fa-hacker-news-square:before {
+  content: "\f3af";
+}
+
+.fa-hackerrank:before {
+  content: "\f5f7";
+}
+
+.fa-hamburger:before {
+  content: "\f805";
+}
+
+.fa-hammer:before {
+  content: "\f6e3";
+}
+
+.fa-hamsa:before {
+  content: "\f665";
+}
+
+.fa-hand-holding:before {
+  content: "\f4bd";
+}
+
+.fa-hand-holding-heart:before {
+  content: "\f4be";
+}
+
+.fa-hand-holding-medical:before {
+  content: "\e05c";
+}
+
+.fa-hand-holding-usd:before {
+  content: "\f4c0";
+}
+
+.fa-hand-holding-water:before {
+  content: "\f4c1";
+}
+
+.fa-hand-lizard:before {
+  content: "\f258";
+}
+
+.fa-hand-middle-finger:before {
+  content: "\f806";
+}
+
+.fa-hand-paper:before {
+  content: "\f256";
+}
+
+.fa-hand-peace:before {
+  content: "\f25b";
+}
+
+.fa-hand-point-down:before {
+  content: "\f0a7";
+}
+
+.fa-hand-point-left:before {
+  content: "\f0a5";
+}
+
+.fa-hand-point-right:before {
+  content: "\f0a4";
+}
+
+.fa-hand-point-up:before {
+  content: "\f0a6";
+}
+
+.fa-hand-pointer:before {
+  content: "\f25a";
+}
+
+.fa-hand-rock:before {
+  content: "\f255";
+}
+
+.fa-hand-scissors:before {
+  content: "\f257";
+}
+
+.fa-hand-sparkles:before {
+  content: "\e05d";
+}
+
+.fa-hand-spock:before {
+  content: "\f259";
+}
+
+.fa-hands:before {
+  content: "\f4c2";
+}
+
+.fa-hands-helping:before {
+  content: "\f4c4";
+}
+
+.fa-hands-wash:before {
+  content: "\e05e";
+}
+
+.fa-handshake:before {
+  content: "\f2b5";
+}
+
+.fa-handshake-alt-slash:before {
+  content: "\e05f";
+}
+
+.fa-handshake-slash:before {
+  content: "\e060";
+}
+
+.fa-hanukiah:before {
+  content: "\f6e6";
+}
+
+.fa-hard-hat:before {
+  content: "\f807";
+}
+
+.fa-hashtag:before {
+  content: "\f292";
+}
+
+.fa-hat-cowboy:before {
+  content: "\f8c0";
+}
+
+.fa-hat-cowboy-side:before {
+  content: "\f8c1";
+}
+
+.fa-hat-wizard:before {
+  content: "\f6e8";
+}
+
+.fa-hdd:before {
+  content: "\f0a0";
+}
+
+.fa-head-side-cough:before {
+  content: "\e061";
+}
+
+.fa-head-side-cough-slash:before {
+  content: "\e062";
+}
+
+.fa-head-side-mask:before {
+  content: "\e063";
+}
+
+.fa-head-side-virus:before {
+  content: "\e064";
+}
+
+.fa-heading:before {
+  content: "\f1dc";
+}
+
+.fa-headphones:before {
+  content: "\f025";
+}
+
+.fa-headphones-alt:before {
+  content: "\f58f";
+}
+
+.fa-headset:before {
+  content: "\f590";
+}
+
+.fa-heart:before {
+  content: "\f004";
+}
+
+.fa-heart-broken:before {
+  content: "\f7a9";
+}
+
+.fa-heartbeat:before {
+  content: "\f21e";
+}
+
+.fa-helicopter:before {
+  content: "\f533";
+}
+
+.fa-highlighter:before {
+  content: "\f591";
+}
+
+.fa-hiking:before {
+  content: "\f6ec";
+}
+
+.fa-hippo:before {
+  content: "\f6ed";
+}
+
+.fa-hips:before {
+  content: "\f452";
+}
+
+.fa-hire-a-helper:before {
+  content: "\f3b0";
+}
+
+.fa-history:before {
+  content: "\f1da";
+}
+
+.fa-hive:before {
+  content: "\e07f";
+}
+
+.fa-hockey-puck:before {
+  content: "\f453";
+}
+
+.fa-holly-berry:before {
+  content: "\f7aa";
+}
+
+.fa-home:before {
+  content: "\f015";
+}
+
+.fa-hooli:before {
+  content: "\f427";
+}
+
+.fa-hornbill:before {
+  content: "\f592";
+}
+
+.fa-horse:before {
+  content: "\f6f0";
+}
+
+.fa-horse-head:before {
+  content: "\f7ab";
+}
+
+.fa-hospital:before {
+  content: "\f0f8";
+}
+
+.fa-hospital-alt:before {
+  content: "\f47d";
+}
+
+.fa-hospital-symbol:before {
+  content: "\f47e";
+}
+
+.fa-hospital-user:before {
+  content: "\f80d";
+}
+
+.fa-hot-tub:before {
+  content: "\f593";
+}
+
+.fa-hotdog:before {
+  content: "\f80f";
+}
+
+.fa-hotel:before {
+  content: "\f594";
+}
+
+.fa-hotjar:before {
+  content: "\f3b1";
+}
+
+.fa-hourglass:before {
+  content: "\f254";
+}
+
+.fa-hourglass-end:before {
+  content: "\f253";
+}
+
+.fa-hourglass-half:before {
+  content: "\f252";
+}
+
+.fa-hourglass-start:before {
+  content: "\f251";
+}
+
+.fa-house-damage:before {
+  content: "\f6f1";
+}
+
+.fa-house-user:before {
+  content: "\e065";
+}
+
+.fa-houzz:before {
+  content: "\f27c";
+}
+
+.fa-hryvnia:before {
+  content: "\f6f2";
+}
+
+.fa-html5:before {
+  content: "\f13b";
+}
+
+.fa-hubspot:before {
+  content: "\f3b2";
+}
+
+.fa-i-cursor:before {
+  content: "\f246";
+}
+
+.fa-ice-cream:before {
+  content: "\f810";
+}
+
+.fa-icicles:before {
+  content: "\f7ad";
+}
+
+.fa-icons:before {
+  content: "\f86d";
+}
+
+.fa-id-badge:before {
+  content: "\f2c1";
+}
+
+.fa-id-card:before {
+  content: "\f2c2";
+}
+
+.fa-id-card-alt:before {
+  content: "\f47f";
+}
+
+.fa-ideal:before {
+  content: "\e013";
+}
+
+.fa-igloo:before {
+  content: "\f7ae";
+}
+
+.fa-image:before {
+  content: "\f03e";
+}
+
+.fa-images:before {
+  content: "\f302";
+}
+
+.fa-imdb:before {
+  content: "\f2d8";
+}
+
+.fa-inbox:before {
+  content: "\f01c";
+}
+
+.fa-indent:before {
+  content: "\f03c";
+}
+
+.fa-industry:before {
+  content: "\f275";
+}
+
+.fa-infinity:before {
+  content: "\f534";
+}
+
+.fa-info:before {
+  content: "\f129";
+}
+
+.fa-info-circle:before {
+  content: "\f05a";
+}
+
+.fa-innosoft:before {
+  content: "\e080";
+}
+
+.fa-instagram:before {
+  content: "\f16d";
+}
+
+.fa-instagram-square:before {
+  content: "\e055";
+}
+
+.fa-instalod:before {
+  content: "\e081";
+}
+
+.fa-intercom:before {
+  content: "\f7af";
+}
+
+.fa-internet-explorer:before {
+  content: "\f26b";
+}
+
+.fa-invision:before {
+  content: "\f7b0";
+}
+
+.fa-ioxhost:before {
+  content: "\f208";
+}
+
+.fa-italic:before {
+  content: "\f033";
+}
+
+.fa-itch-io:before {
+  content: "\f83a";
+}
+
+.fa-itunes:before {
+  content: "\f3b4";
+}
+
+.fa-itunes-note:before {
+  content: "\f3b5";
+}
+
+.fa-java:before {
+  content: "\f4e4";
+}
+
+.fa-jedi:before {
+  content: "\f669";
+}
+
+.fa-jedi-order:before {
+  content: "\f50e";
+}
+
+.fa-jenkins:before {
+  content: "\f3b6";
+}
+
+.fa-jira:before {
+  content: "\f7b1";
+}
+
+.fa-joget:before {
+  content: "\f3b7";
+}
+
+.fa-joint:before {
+  content: "\f595";
+}
+
+.fa-joomla:before {
+  content: "\f1aa";
+}
+
+.fa-journal-whills:before {
+  content: "\f66a";
+}
+
+.fa-js:before {
+  content: "\f3b8";
+}
+
+.fa-js-square:before {
+  content: "\f3b9";
+}
+
+.fa-jsfiddle:before {
+  content: "\f1cc";
+}
+
+.fa-kaaba:before {
+  content: "\f66b";
+}
+
+.fa-kaggle:before {
+  content: "\f5fa";
+}
+
+.fa-key:before {
+  content: "\f084";
+}
+
+.fa-keybase:before {
+  content: "\f4f5";
+}
+
+.fa-keyboard:before {
+  content: "\f11c";
+}
+
+.fa-keycdn:before {
+  content: "\f3ba";
+}
+
+.fa-khanda:before {
+  content: "\f66d";
+}
+
+.fa-kickstarter:before {
+  content: "\f3bb";
+}
+
+.fa-kickstarter-k:before {
+  content: "\f3bc";
+}
+
+.fa-kiss:before {
+  content: "\f596";
+}
+
+.fa-kiss-beam:before {
+  content: "\f597";
+}
+
+.fa-kiss-wink-heart:before {
+  content: "\f598";
+}
+
+.fa-kiwi-bird:before {
+  content: "\f535";
+}
+
+.fa-korvue:before {
+  content: "\f42f";
+}
+
+.fa-landmark:before {
+  content: "\f66f";
+}
+
+.fa-language:before {
+  content: "\f1ab";
+}
+
+.fa-laptop:before {
+  content: "\f109";
+}
+
+.fa-laptop-code:before {
+  content: "\f5fc";
+}
+
+.fa-laptop-house:before {
+  content: "\e066";
+}
+
+.fa-laptop-medical:before {
+  content: "\f812";
+}
+
+.fa-laravel:before {
+  content: "\f3bd";
+}
+
+.fa-lastfm:before {
+  content: "\f202";
+}
+
+.fa-lastfm-square:before {
+  content: "\f203";
+}
+
+.fa-laugh:before {
+  content: "\f599";
+}
+
+.fa-laugh-beam:before {
+  content: "\f59a";
+}
+
+.fa-laugh-squint:before {
+  content: "\f59b";
+}
+
+.fa-laugh-wink:before {
+  content: "\f59c";
+}
+
+.fa-layer-group:before {
+  content: "\f5fd";
+}
+
+.fa-leaf:before {
+  content: "\f06c";
+}
+
+.fa-leanpub:before {
+  content: "\f212";
+}
+
+.fa-lemon:before {
+  content: "\f094";
+}
+
+.fa-less:before {
+  content: "\f41d";
+}
+
+.fa-less-than:before {
+  content: "\f536";
+}
+
+.fa-less-than-equal:before {
+  content: "\f537";
+}
+
+.fa-level-down-alt:before {
+  content: "\f3be";
+}
+
+.fa-level-up-alt:before {
+  content: "\f3bf";
+}
+
+.fa-life-ring:before {
+  content: "\f1cd";
+}
+
+.fa-lightbulb:before {
+  content: "\f0eb";
+}
+
+.fa-line:before {
+  content: "\f3c0";
+}
+
+.fa-link:before {
+  content: "\f0c1";
+}
+
+.fa-linkedin:before {
+  content: "\f08c";
+}
+
+.fa-linkedin-in:before {
+  content: "\f0e1";
+}
+
+.fa-linode:before {
+  content: "\f2b8";
+}
+
+.fa-linux:before {
+  content: "\f17c";
+}
+
+.fa-lira-sign:before {
+  content: "\f195";
+}
+
+.fa-list:before {
+  content: "\f03a";
+}
+
+.fa-list-alt:before {
+  content: "\f022";
+}
+
+.fa-list-ol:before {
+  content: "\f0cb";
+}
+
+.fa-list-ul:before {
+  content: "\f0ca";
+}
+
+.fa-location-arrow:before {
+  content: "\f124";
+}
+
+.fa-lock:before {
+  content: "\f023";
+}
+
+.fa-lock-open:before {
+  content: "\f3c1";
+}
+
+.fa-long-arrow-alt-down:before {
+  content: "\f309";
+}
+
+.fa-long-arrow-alt-left:before {
+  content: "\f30a";
+}
+
+.fa-long-arrow-alt-right:before {
+  content: "\f30b";
+}
+
+.fa-long-arrow-alt-up:before {
+  content: "\f30c";
+}
+
+.fa-low-vision:before {
+  content: "\f2a8";
+}
+
+.fa-luggage-cart:before {
+  content: "\f59d";
+}
+
+.fa-lungs:before {
+  content: "\f604";
+}
+
+.fa-lungs-virus:before {
+  content: "\e067";
+}
+
+.fa-lyft:before {
+  content: "\f3c3";
+}
+
+.fa-magento:before {
+  content: "\f3c4";
+}
+
+.fa-magic:before {
+  content: "\f0d0";
+}
+
+.fa-magnet:before {
+  content: "\f076";
+}
+
+.fa-mail-bulk:before {
+  content: "\f674";
+}
+
+.fa-mailchimp:before {
+  content: "\f59e";
+}
+
+.fa-male:before {
+  content: "\f183";
+}
+
+.fa-mandalorian:before {
+  content: "\f50f";
+}
+
+.fa-map:before {
+  content: "\f279";
+}
+
+.fa-map-marked:before {
+  content: "\f59f";
+}
+
+.fa-map-marked-alt:before {
+  content: "\f5a0";
+}
+
+.fa-map-marker:before {
+  content: "\f041";
+}
+
+.fa-map-marker-alt:before {
+  content: "\f3c5";
+}
+
+.fa-map-pin:before {
+  content: "\f276";
+}
+
+.fa-map-signs:before {
+  content: "\f277";
+}
+
+.fa-markdown:before {
+  content: "\f60f";
+}
+
+.fa-marker:before {
+  content: "\f5a1";
+}
+
+.fa-mars:before {
+  content: "\f222";
+}
+
+.fa-mars-double:before {
+  content: "\f227";
+}
+
+.fa-mars-stroke:before {
+  content: "\f229";
+}
+
+.fa-mars-stroke-h:before {
+  content: "\f22b";
+}
+
+.fa-mars-stroke-v:before {
+  content: "\f22a";
+}
+
+.fa-mask:before {
+  content: "\f6fa";
+}
+
+.fa-mastodon:before {
+  content: "\f4f6";
+}
+
+.fa-maxcdn:before {
+  content: "\f136";
+}
+
+.fa-mdb:before {
+  content: "\f8ca";
+}
+
+.fa-medal:before {
+  content: "\f5a2";
+}
+
+.fa-medapps:before {
+  content: "\f3c6";
+}
+
+.fa-medium:before {
+  content: "\f23a";
+}
+
+.fa-medium-m:before {
+  content: "\f3c7";
+}
+
+.fa-medkit:before {
+  content: "\f0fa";
+}
+
+.fa-medrt:before {
+  content: "\f3c8";
+}
+
+.fa-meetup:before {
+  content: "\f2e0";
+}
+
+.fa-megaport:before {
+  content: "\f5a3";
+}
+
+.fa-meh:before {
+  content: "\f11a";
+}
+
+.fa-meh-blank:before {
+  content: "\f5a4";
+}
+
+.fa-meh-rolling-eyes:before {
+  content: "\f5a5";
+}
+
+.fa-memory:before {
+  content: "\f538";
+}
+
+.fa-mendeley:before {
+  content: "\f7b3";
+}
+
+.fa-menorah:before {
+  content: "\f676";
+}
+
+.fa-mercury:before {
+  content: "\f223";
+}
+
+.fa-meteor:before {
+  content: "\f753";
+}
+
+.fa-microblog:before {
+  content: "\e01a";
+}
+
+.fa-microchip:before {
+  content: "\f2db";
+}
+
+.fa-microphone:before {
+  content: "\f130";
+}
+
+.fa-microphone-alt:before {
+  content: "\f3c9";
+}
+
+.fa-microphone-alt-slash:before {
+  content: "\f539";
+}
+
+.fa-microphone-slash:before {
+  content: "\f131";
+}
+
+.fa-microscope:before {
+  content: "\f610";
+}
+
+.fa-microsoft:before {
+  content: "\f3ca";
+}
+
+.fa-minus:before {
+  content: "\f068";
+}
+
+.fa-minus-circle:before {
+  content: "\f056";
+}
+
+.fa-minus-square:before {
+  content: "\f146";
+}
+
+.fa-mitten:before {
+  content: "\f7b5";
+}
+
+.fa-mix:before {
+  content: "\f3cb";
+}
+
+.fa-mixcloud:before {
+  content: "\f289";
+}
+
+.fa-mixer:before {
+  content: "\e056";
+}
+
+.fa-mizuni:before {
+  content: "\f3cc";
+}
+
+.fa-mobile:before {
+  content: "\f10b";
+}
+
+.fa-mobile-alt:before {
+  content: "\f3cd";
+}
+
+.fa-modx:before {
+  content: "\f285";
+}
+
+.fa-monero:before {
+  content: "\f3d0";
+}
+
+.fa-money-bill:before {
+  content: "\f0d6";
+}
+
+.fa-money-bill-alt:before {
+  content: "\f3d1";
+}
+
+.fa-money-bill-wave:before {
+  content: "\f53a";
+}
+
+.fa-money-bill-wave-alt:before {
+  content: "\f53b";
+}
+
+.fa-money-check:before {
+  content: "\f53c";
+}
+
+.fa-money-check-alt:before {
+  content: "\f53d";
+}
+
+.fa-monument:before {
+  content: "\f5a6";
+}
+
+.fa-moon:before {
+  content: "\f186";
+}
+
+.fa-mortar-pestle:before {
+  content: "\f5a7";
+}
+
+.fa-mosque:before {
+  content: "\f678";
+}
+
+.fa-motorcycle:before {
+  content: "\f21c";
+}
+
+.fa-mountain:before {
+  content: "\f6fc";
+}
+
+.fa-mouse:before {
+  content: "\f8cc";
+}
+
+.fa-mouse-pointer:before {
+  content: "\f245";
+}
+
+.fa-mug-hot:before {
+  content: "\f7b6";
+}
+
+.fa-music:before {
+  content: "\f001";
+}
+
+.fa-napster:before {
+  content: "\f3d2";
+}
+
+.fa-neos:before {
+  content: "\f612";
+}
+
+.fa-network-wired:before {
+  content: "\f6ff";
+}
+
+.fa-neuter:before {
+  content: "\f22c";
+}
+
+.fa-newspaper:before {
+  content: "\f1ea";
+}
+
+.fa-nimblr:before {
+  content: "\f5a8";
+}
+
+.fa-node:before {
+  content: "\f419";
+}
+
+.fa-node-js:before {
+  content: "\f3d3";
+}
+
+.fa-not-equal:before {
+  content: "\f53e";
+}
+
+.fa-notes-medical:before {
+  content: "\f481";
+}
+
+.fa-npm:before {
+  content: "\f3d4";
+}
+
+.fa-ns8:before {
+  content: "\f3d5";
+}
+
+.fa-nutritionix:before {
+  content: "\f3d6";
+}
+
+.fa-object-group:before {
+  content: "\f247";
+}
+
+.fa-object-ungroup:before {
+  content: "\f248";
+}
+
+.fa-octopus-deploy:before {
+  content: "\e082";
+}
+
+.fa-odnoklassniki:before {
+  content: "\f263";
+}
+
+.fa-odnoklassniki-square:before {
+  content: "\f264";
+}
+
+.fa-oil-can:before {
+  content: "\f613";
+}
+
+.fa-old-republic:before {
+  content: "\f510";
+}
+
+.fa-om:before {
+  content: "\f679";
+}
+
+.fa-opencart:before {
+  content: "\f23d";
+}
+
+.fa-openid:before {
+  content: "\f19b";
+}
+
+.fa-opera:before {
+  content: "\f26a";
+}
+
+.fa-optin-monster:before {
+  content: "\f23c";
+}
+
+.fa-orcid:before {
+  content: "\f8d2";
+}
+
+.fa-osi:before {
+  content: "\f41a";
+}
+
+.fa-otter:before {
+  content: "\f700";
+}
+
+.fa-outdent:before {
+  content: "\f03b";
+}
+
+.fa-page4:before {
+  content: "\f3d7";
+}
+
+.fa-pagelines:before {
+  content: "\f18c";
+}
+
+.fa-pager:before {
+  content: "\f815";
+}
+
+.fa-paint-brush:before {
+  content: "\f1fc";
+}
+
+.fa-paint-roller:before {
+  content: "\f5aa";
+}
+
+.fa-palette:before {
+  content: "\f53f";
+}
+
+.fa-palfed:before {
+  content: "\f3d8";
+}
+
+.fa-pallet:before {
+  content: "\f482";
+}
+
+.fa-paper-plane:before {
+  content: "\f1d8";
+}
+
+.fa-paperclip:before {
+  content: "\f0c6";
+}
+
+.fa-parachute-box:before {
+  content: "\f4cd";
+}
+
+.fa-paragraph:before {
+  content: "\f1dd";
+}
+
+.fa-parking:before {
+  content: "\f540";
+}
+
+.fa-passport:before {
+  content: "\f5ab";
+}
+
+.fa-pastafarianism:before {
+  content: "\f67b";
+}
+
+.fa-paste:before {
+  content: "\f0ea";
+}
+
+.fa-patreon:before {
+  content: "\f3d9";
+}
+
+.fa-pause:before {
+  content: "\f04c";
+}
+
+.fa-pause-circle:before {
+  content: "\f28b";
+}
+
+.fa-paw:before {
+  content: "\f1b0";
+}
+
+.fa-paypal:before {
+  content: "\f1ed";
+}
+
+.fa-peace:before {
+  content: "\f67c";
+}
+
+.fa-pen:before {
+  content: "\f304";
+}
+
+.fa-pen-alt:before {
+  content: "\f305";
+}
+
+.fa-pen-fancy:before {
+  content: "\f5ac";
+}
+
+.fa-pen-nib:before {
+  content: "\f5ad";
+}
+
+.fa-pen-square:before {
+  content: "\f14b";
+}
+
+.fa-pencil-alt:before {
+  content: "\f303";
+}
+
+.fa-pencil-ruler:before {
+  content: "\f5ae";
+}
+
+.fa-penny-arcade:before {
+  content: "\f704";
+}
+
+.fa-people-arrows:before {
+  content: "\e068";
+}
+
+.fa-people-carry:before {
+  content: "\f4ce";
+}
+
+.fa-pepper-hot:before {
+  content: "\f816";
+}
+
+.fa-perbyte:before {
+  content: "\e083";
+}
+
+.fa-percent:before {
+  content: "\f295";
+}
+
+.fa-percentage:before {
+  content: "\f541";
+}
+
+.fa-periscope:before {
+  content: "\f3da";
+}
+
+.fa-person-booth:before {
+  content: "\f756";
+}
+
+.fa-phabricator:before {
+  content: "\f3db";
+}
+
+.fa-phoenix-framework:before {
+  content: "\f3dc";
+}
+
+.fa-phoenix-squadron:before {
+  content: "\f511";
+}
+
+.fa-phone:before {
+  content: "\f095";
+}
+
+.fa-phone-alt:before {
+  content: "\f879";
+}
+
+.fa-phone-slash:before {
+  content: "\f3dd";
+}
+
+.fa-phone-square:before {
+  content: "\f098";
+}
+
+.fa-phone-square-alt:before {
+  content: "\f87b";
+}
+
+.fa-phone-volume:before {
+  content: "\f2a0";
+}
+
+.fa-photo-video:before {
+  content: "\f87c";
+}
+
+.fa-php:before {
+  content: "\f457";
+}
+
+.fa-pied-piper:before {
+  content: "\f2ae";
+}
+
+.fa-pied-piper-alt:before {
+  content: "\f1a8";
+}
+
+.fa-pied-piper-hat:before {
+  content: "\f4e5";
+}
+
+.fa-pied-piper-pp:before {
+  content: "\f1a7";
+}
+
+.fa-pied-piper-square:before {
+  content: "\e01e";
+}
+
+.fa-piggy-bank:before {
+  content: "\f4d3";
+}
+
+.fa-pills:before {
+  content: "\f484";
+}
+
+.fa-pinterest:before {
+  content: "\f0d2";
+}
+
+.fa-pinterest-p:before {
+  content: "\f231";
+}
+
+.fa-pinterest-square:before {
+  content: "\f0d3";
+}
+
+.fa-pizza-slice:before {
+  content: "\f818";
+}
+
+.fa-place-of-worship:before {
+  content: "\f67f";
+}
+
+.fa-plane:before {
+  content: "\f072";
+}
+
+.fa-plane-arrival:before {
+  content: "\f5af";
+}
+
+.fa-plane-departure:before {
+  content: "\f5b0";
+}
+
+.fa-plane-slash:before {
+  content: "\e069";
+}
+
+.fa-play:before {
+  content: "\f04b";
+}
+
+.fa-play-circle:before {
+  content: "\f144";
+}
+
+.fa-playstation:before {
+  content: "\f3df";
+}
+
+.fa-plug:before {
+  content: "\f1e6";
+}
+
+.fa-plus:before {
+  content: "\f067";
+}
+
+.fa-plus-circle:before {
+  content: "\f055";
+}
+
+.fa-plus-square:before {
+  content: "\f0fe";
+}
+
+.fa-podcast:before {
+  content: "\f2ce";
+}
+
+.fa-poll:before {
+  content: "\f681";
+}
+
+.fa-poll-h:before {
+  content: "\f682";
+}
+
+.fa-poo:before {
+  content: "\f2fe";
+}
+
+.fa-poo-storm:before {
+  content: "\f75a";
+}
+
+.fa-poop:before {
+  content: "\f619";
+}
+
+.fa-portrait:before {
+  content: "\f3e0";
+}
+
+.fa-pound-sign:before {
+  content: "\f154";
+}
+
+.fa-power-off:before {
+  content: "\f011";
+}
+
+.fa-pray:before {
+  content: "\f683";
+}
+
+.fa-praying-hands:before {
+  content: "\f684";
+}
+
+.fa-prescription:before {
+  content: "\f5b1";
+}
+
+.fa-prescription-bottle:before {
+  content: "\f485";
+}
+
+.fa-prescription-bottle-alt:before {
+  content: "\f486";
+}
+
+.fa-print:before {
+  content: "\f02f";
+}
+
+.fa-procedures:before {
+  content: "\f487";
+}
+
+.fa-product-hunt:before {
+  content: "\f288";
+}
+
+.fa-project-diagram:before {
+  content: "\f542";
+}
+
+.fa-pump-medical:before {
+  content: "\e06a";
+}
+
+.fa-pump-soap:before {
+  content: "\e06b";
+}
+
+.fa-pushed:before {
+  content: "\f3e1";
+}
+
+.fa-puzzle-piece:before {
+  content: "\f12e";
+}
+
+.fa-python:before {
+  content: "\f3e2";
+}
+
+.fa-qq:before {
+  content: "\f1d6";
+}
+
+.fa-qrcode:before {
+  content: "\f029";
+}
+
+.fa-question:before {
+  content: "\f128";
+}
+
+.fa-question-circle:before {
+  content: "\f059";
+}
+
+.fa-quidditch:before {
+  content: "\f458";
+}
+
+.fa-quinscape:before {
+  content: "\f459";
+}
+
+.fa-quora:before {
+  content: "\f2c4";
+}
+
+.fa-quote-left:before {
+  content: "\f10d";
+}
+
+.fa-quote-right:before {
+  content: "\f10e";
+}
+
+.fa-quran:before {
+  content: "\f687";
+}
+
+.fa-r-project:before {
+  content: "\f4f7";
+}
+
+.fa-radiation:before {
+  content: "\f7b9";
+}
+
+.fa-radiation-alt:before {
+  content: "\f7ba";
+}
+
+.fa-rainbow:before {
+  content: "\f75b";
+}
+
+.fa-random:before {
+  content: "\f074";
+}
+
+.fa-raspberry-pi:before {
+  content: "\f7bb";
+}
+
+.fa-ravelry:before {
+  content: "\f2d9";
+}
+
+.fa-react:before {
+  content: "\f41b";
+}
+
+.fa-reacteurope:before {
+  content: "\f75d";
+}
+
+.fa-readme:before {
+  content: "\f4d5";
+}
+
+.fa-rebel:before {
+  content: "\f1d0";
+}
+
+.fa-receipt:before {
+  content: "\f543";
+}
+
+.fa-record-vinyl:before {
+  content: "\f8d9";
+}
+
+.fa-recycle:before {
+  content: "\f1b8";
+}
+
+.fa-red-river:before {
+  content: "\f3e3";
+}
+
+.fa-reddit:before {
+  content: "\f1a1";
+}
+
+.fa-reddit-alien:before {
+  content: "\f281";
+}
+
+.fa-reddit-square:before {
+  content: "\f1a2";
+}
+
+.fa-redhat:before {
+  content: "\f7bc";
+}
+
+.fa-redo:before {
+  content: "\f01e";
+}
+
+.fa-redo-alt:before {
+  content: "\f2f9";
+}
+
+.fa-registered:before {
+  content: "\f25d";
+}
+
+.fa-remove-format:before {
+  content: "\f87d";
+}
+
+.fa-renren:before {
+  content: "\f18b";
+}
+
+.fa-reply:before {
+  content: "\f3e5";
+}
+
+.fa-reply-all:before {
+  content: "\f122";
+}
+
+.fa-replyd:before {
+  content: "\f3e6";
+}
+
+.fa-republican:before {
+  content: "\f75e";
+}
+
+.fa-researchgate:before {
+  content: "\f4f8";
+}
+
+.fa-resolving:before {
+  content: "\f3e7";
+}
+
+.fa-restroom:before {
+  content: "\f7bd";
+}
+
+.fa-retweet:before {
+  content: "\f079";
+}
+
+.fa-rev:before {
+  content: "\f5b2";
+}
+
+.fa-ribbon:before {
+  content: "\f4d6";
+}
+
+.fa-ring:before {
+  content: "\f70b";
+}
+
+.fa-road:before {
+  content: "\f018";
+}
+
+.fa-robot:before {
+  content: "\f544";
+}
+
+.fa-rocket:before {
+  content: "\f135";
+}
+
+.fa-rocketchat:before {
+  content: "\f3e8";
+}
+
+.fa-rockrms:before {
+  content: "\f3e9";
+}
+
+.fa-route:before {
+  content: "\f4d7";
+}
+
+.fa-rss:before {
+  content: "\f09e";
+}
+
+.fa-rss-square:before {
+  content: "\f143";
+}
+
+.fa-ruble-sign:before {
+  content: "\f158";
+}
+
+.fa-ruler:before {
+  content: "\f545";
+}
+
+.fa-ruler-combined:before {
+  content: "\f546";
+}
+
+.fa-ruler-horizontal:before {
+  content: "\f547";
+}
+
+.fa-ruler-vertical:before {
+  content: "\f548";
+}
+
+.fa-running:before {
+  content: "\f70c";
+}
+
+.fa-rupee-sign:before {
+  content: "\f156";
+}
+
+.fa-rust:before {
+  content: "\e07a";
+}
+
+.fa-sad-cry:before {
+  content: "\f5b3";
+}
+
+.fa-sad-tear:before {
+  content: "\f5b4";
+}
+
+.fa-safari:before {
+  content: "\f267";
+}
+
+.fa-salesforce:before {
+  content: "\f83b";
+}
+
+.fa-sass:before {
+  content: "\f41e";
+}
+
+.fa-satellite:before {
+  content: "\f7bf";
+}
+
+.fa-satellite-dish:before {
+  content: "\f7c0";
+}
+
+.fa-save:before {
+  content: "\f0c7";
+}
+
+.fa-schlix:before {
+  content: "\f3ea";
+}
+
+.fa-school:before {
+  content: "\f549";
+}
+
+.fa-screwdriver:before {
+  content: "\f54a";
+}
+
+.fa-scribd:before {
+  content: "\f28a";
+}
+
+.fa-scroll:before {
+  content: "\f70e";
+}
+
+.fa-sd-card:before {
+  content: "\f7c2";
+}
+
+.fa-search:before {
+  content: "\f002";
+}
+
+.fa-search-dollar:before {
+  content: "\f688";
+}
+
+.fa-search-location:before {
+  content: "\f689";
+}
+
+.fa-search-minus:before {
+  content: "\f010";
+}
+
+.fa-search-plus:before {
+  content: "\f00e";
+}
+
+.fa-searchengin:before {
+  content: "\f3eb";
+}
+
+.fa-seedling:before {
+  content: "\f4d8";
+}
+
+.fa-sellcast:before {
+  content: "\f2da";
+}
+
+.fa-sellsy:before {
+  content: "\f213";
+}
+
+.fa-server:before {
+  content: "\f233";
+}
+
+.fa-servicestack:before {
+  content: "\f3ec";
+}
+
+.fa-shapes:before {
+  content: "\f61f";
+}
+
+.fa-share:before {
+  content: "\f064";
+}
+
+.fa-share-alt:before {
+  content: "\f1e0";
+}
+
+.fa-share-alt-square:before {
+  content: "\f1e1";
+}
+
+.fa-share-square:before {
+  content: "\f14d";
+}
+
+.fa-shekel-sign:before {
+  content: "\f20b";
+}
+
+.fa-shield-alt:before {
+  content: "\f3ed";
+}
+
+.fa-shield-virus:before {
+  content: "\e06c";
+}
+
+.fa-ship:before {
+  content: "\f21a";
+}
+
+.fa-shipping-fast:before {
+  content: "\f48b";
+}
+
+.fa-shirtsinbulk:before {
+  content: "\f214";
+}
+
+.fa-shoe-prints:before {
+  content: "\f54b";
+}
+
+.fa-shopify:before {
+  content: "\e057";
+}
+
+.fa-shopping-bag:before {
+  content: "\f290";
+}
+
+.fa-shopping-basket:before {
+  content: "\f291";
+}
+
+.fa-shopping-cart:before {
+  content: "\f07a";
+}
+
+.fa-shopware:before {
+  content: "\f5b5";
+}
+
+.fa-shower:before {
+  content: "\f2cc";
+}
+
+.fa-shuttle-van:before {
+  content: "\f5b6";
+}
+
+.fa-sign:before {
+  content: "\f4d9";
+}
+
+.fa-sign-in-alt:before {
+  content: "\f2f6";
+}
+
+.fa-sign-language:before {
+  content: "\f2a7";
+}
+
+.fa-sign-out-alt:before {
+  content: "\f2f5";
+}
+
+.fa-signal:before {
+  content: "\f012";
+}
+
+.fa-signature:before {
+  content: "\f5b7";
+}
+
+.fa-sim-card:before {
+  content: "\f7c4";
+}
+
+.fa-simplybuilt:before {
+  content: "\f215";
+}
+
+.fa-sink:before {
+  content: "\e06d";
+}
+
+.fa-sistrix:before {
+  content: "\f3ee";
+}
+
+.fa-sitemap:before {
+  content: "\f0e8";
+}
+
+.fa-sith:before {
+  content: "\f512";
+}
+
+.fa-skating:before {
+  content: "\f7c5";
+}
+
+.fa-sketch:before {
+  content: "\f7c6";
+}
+
+.fa-skiing:before {
+  content: "\f7c9";
+}
+
+.fa-skiing-nordic:before {
+  content: "\f7ca";
+}
+
+.fa-skull:before {
+  content: "\f54c";
+}
+
+.fa-skull-crossbones:before {
+  content: "\f714";
+}
+
+.fa-skyatlas:before {
+  content: "\f216";
+}
+
+.fa-skype:before {
+  content: "\f17e";
+}
+
+.fa-slack:before {
+  content: "\f198";
+}
+
+.fa-slack-hash:before {
+  content: "\f3ef";
+}
+
+.fa-slash:before {
+  content: "\f715";
+}
+
+.fa-sleigh:before {
+  content: "\f7cc";
+}
+
+.fa-sliders-h:before {
+  content: "\f1de";
+}
+
+.fa-slideshare:before {
+  content: "\f1e7";
+}
+
+.fa-smile:before {
+  content: "\f118";
+}
+
+.fa-smile-beam:before {
+  content: "\f5b8";
+}
+
+.fa-smile-wink:before {
+  content: "\f4da";
+}
+
+.fa-smog:before {
+  content: "\f75f";
+}
+
+.fa-smoking:before {
+  content: "\f48d";
+}
+
+.fa-smoking-ban:before {
+  content: "\f54d";
+}
+
+.fa-sms:before {
+  content: "\f7cd";
+}
+
+.fa-snapchat:before {
+  content: "\f2ab";
+}
+
+.fa-snapchat-ghost:before {
+  content: "\f2ac";
+}
+
+.fa-snapchat-square:before {
+  content: "\f2ad";
+}
+
+.fa-snowboarding:before {
+  content: "\f7ce";
+}
+
+.fa-snowflake:before {
+  content: "\f2dc";
+}
+
+.fa-snowman:before {
+  content: "\f7d0";
+}
+
+.fa-snowplow:before {
+  content: "\f7d2";
+}
+
+.fa-soap:before {
+  content: "\e06e";
+}
+
+.fa-socks:before {
+  content: "\f696";
+}
+
+.fa-solar-panel:before {
+  content: "\f5ba";
+}
+
+.fa-sort:before {
+  content: "\f0dc";
+}
+
+.fa-sort-alpha-down:before {
+  content: "\f15d";
+}
+
+.fa-sort-alpha-down-alt:before {
+  content: "\f881";
+}
+
+.fa-sort-alpha-up:before {
+  content: "\f15e";
+}
+
+.fa-sort-alpha-up-alt:before {
+  content: "\f882";
+}
+
+.fa-sort-amount-down:before {
+  content: "\f160";
+}
+
+.fa-sort-amount-down-alt:before {
+  content: "\f884";
+}
+
+.fa-sort-amount-up:before {
+  content: "\f161";
+}
+
+.fa-sort-amount-up-alt:before {
+  content: "\f885";
+}
+
+.fa-sort-down:before {
+  content: "\f0dd";
+}
+
+.fa-sort-numeric-down:before {
+  content: "\f162";
+}
+
+.fa-sort-numeric-down-alt:before {
+  content: "\f886";
+}
+
+.fa-sort-numeric-up:before {
+  content: "\f163";
+}
+
+.fa-sort-numeric-up-alt:before {
+  content: "\f887";
+}
+
+.fa-sort-up:before {
+  content: "\f0de";
+}
+
+.fa-soundcloud:before {
+  content: "\f1be";
+}
+
+.fa-sourcetree:before {
+  content: "\f7d3";
+}
+
+.fa-spa:before {
+  content: "\f5bb";
+}
+
+.fa-space-shuttle:before {
+  content: "\f197";
+}
+
+.fa-speakap:before {
+  content: "\f3f3";
+}
+
+.fa-speaker-deck:before {
+  content: "\f83c";
+}
+
+.fa-spell-check:before {
+  content: "\f891";
+}
+
+.fa-spider:before {
+  content: "\f717";
+}
+
+.fa-spinner:before {
+  content: "\f110";
+}
+
+.fa-splotch:before {
+  content: "\f5bc";
+}
+
+.fa-spotify:before {
+  content: "\f1bc";
+}
+
+.fa-spray-can:before {
+  content: "\f5bd";
+}
+
+.fa-square:before {
+  content: "\f0c8";
+}
+
+.fa-square-full:before {
+  content: "\f45c";
+}
+
+.fa-square-root-alt:before {
+  content: "\f698";
+}
+
+.fa-squarespace:before {
+  content: "\f5be";
+}
+
+.fa-stack-exchange:before {
+  content: "\f18d";
+}
+
+.fa-stack-overflow:before {
+  content: "\f16c";
+}
+
+.fa-stackpath:before {
+  content: "\f842";
+}
+
+.fa-stamp:before {
+  content: "\f5bf";
+}
+
+.fa-star:before {
+  content: "\f005";
+}
+
+.fa-star-and-crescent:before {
+  content: "\f699";
+}
+
+.fa-star-half:before {
+  content: "\f089";
+}
+
+.fa-star-half-alt:before {
+  content: "\f5c0";
+}
+
+.fa-star-of-david:before {
+  content: "\f69a";
+}
+
+.fa-star-of-life:before {
+  content: "\f621";
+}
+
+.fa-staylinked:before {
+  content: "\f3f5";
+}
+
+.fa-steam:before {
+  content: "\f1b6";
+}
+
+.fa-steam-square:before {
+  content: "\f1b7";
+}
+
+.fa-steam-symbol:before {
+  content: "\f3f6";
+}
+
+.fa-step-backward:before {
+  content: "\f048";
+}
+
+.fa-step-forward:before {
+  content: "\f051";
+}
+
+.fa-stethoscope:before {
+  content: "\f0f1";
+}
+
+.fa-sticker-mule:before {
+  content: "\f3f7";
+}
+
+.fa-sticky-note:before {
+  content: "\f249";
+}
+
+.fa-stop:before {
+  content: "\f04d";
+}
+
+.fa-stop-circle:before {
+  content: "\f28d";
+}
+
+.fa-stopwatch:before {
+  content: "\f2f2";
+}
+
+.fa-stopwatch-20:before {
+  content: "\e06f";
+}
+
+.fa-store:before {
+  content: "\f54e";
+}
+
+.fa-store-alt:before {
+  content: "\f54f";
+}
+
+.fa-store-alt-slash:before {
+  content: "\e070";
+}
+
+.fa-store-slash:before {
+  content: "\e071";
+}
+
+.fa-strava:before {
+  content: "\f428";
+}
+
+.fa-stream:before {
+  content: "\f550";
+}
+
+.fa-street-view:before {
+  content: "\f21d";
+}
+
+.fa-strikethrough:before {
+  content: "\f0cc";
+}
+
+.fa-stripe:before {
+  content: "\f429";
+}
+
+.fa-stripe-s:before {
+  content: "\f42a";
+}
+
+.fa-stroopwafel:before {
+  content: "\f551";
+}
+
+.fa-studiovinari:before {
+  content: "\f3f8";
+}
+
+.fa-stumbleupon:before {
+  content: "\f1a4";
+}
+
+.fa-stumbleupon-circle:before {
+  content: "\f1a3";
+}
+
+.fa-subscript:before {
+  content: "\f12c";
+}
+
+.fa-subway:before {
+  content: "\f239";
+}
+
+.fa-suitcase:before {
+  content: "\f0f2";
+}
+
+.fa-suitcase-rolling:before {
+  content: "\f5c1";
+}
+
+.fa-sun:before {
+  content: "\f185";
+}
+
+.fa-superpowers:before {
+  content: "\f2dd";
+}
+
+.fa-superscript:before {
+  content: "\f12b";
+}
+
+.fa-supple:before {
+  content: "\f3f9";
+}
+
+.fa-surprise:before {
+  content: "\f5c2";
+}
+
+.fa-suse:before {
+  content: "\f7d6";
+}
+
+.fa-swatchbook:before {
+  content: "\f5c3";
+}
+
+.fa-swift:before {
+  content: "\f8e1";
+}
+
+.fa-swimmer:before {
+  content: "\f5c4";
+}
+
+.fa-swimming-pool:before {
+  content: "\f5c5";
+}
+
+.fa-symfony:before {
+  content: "\f83d";
+}
+
+.fa-synagogue:before {
+  content: "\f69b";
+}
+
+.fa-sync:before {
+  content: "\f021";
+}
+
+.fa-sync-alt:before {
+  content: "\f2f1";
+}
+
+.fa-syringe:before {
+  content: "\f48e";
+}
+
+.fa-table:before {
+  content: "\f0ce";
+}
+
+.fa-table-tennis:before {
+  content: "\f45d";
+}
+
+.fa-tablet:before {
+  content: "\f10a";
+}
+
+.fa-tablet-alt:before {
+  content: "\f3fa";
+}
+
+.fa-tablets:before {
+  content: "\f490";
+}
+
+.fa-tachometer-alt:before {
+  content: "\f3fd";
+}
+
+.fa-tag:before {
+  content: "\f02b";
+}
+
+.fa-tags:before {
+  content: "\f02c";
+}
+
+.fa-tape:before {
+  content: "\f4db";
+}
+
+.fa-tasks:before {
+  content: "\f0ae";
+}
+
+.fa-taxi:before {
+  content: "\f1ba";
+}
+
+.fa-teamspeak:before {
+  content: "\f4f9";
+}
+
+.fa-teeth:before {
+  content: "\f62e";
+}
+
+.fa-teeth-open:before {
+  content: "\f62f";
+}
+
+.fa-telegram:before {
+  content: "\f2c6";
+}
+
+.fa-telegram-plane:before {
+  content: "\f3fe";
+}
+
+.fa-temperature-high:before {
+  content: "\f769";
+}
+
+.fa-temperature-low:before {
+  content: "\f76b";
+}
+
+.fa-tencent-weibo:before {
+  content: "\f1d5";
+}
+
+.fa-tenge:before {
+  content: "\f7d7";
+}
+
+.fa-terminal:before {
+  content: "\f120";
+}
+
+.fa-text-height:before {
+  content: "\f034";
+}
+
+.fa-text-width:before {
+  content: "\f035";
+}
+
+.fa-th:before {
+  content: "\f00a";
+}
+
+.fa-th-large:before {
+  content: "\f009";
+}
+
+.fa-th-list:before {
+  content: "\f00b";
+}
+
+.fa-the-red-yeti:before {
+  content: "\f69d";
+}
+
+.fa-theater-masks:before {
+  content: "\f630";
+}
+
+.fa-themeco:before {
+  content: "\f5c6";
+}
+
+.fa-themeisle:before {
+  content: "\f2b2";
+}
+
+.fa-thermometer:before {
+  content: "\f491";
+}
+
+.fa-thermometer-empty:before {
+  content: "\f2cb";
+}
+
+.fa-thermometer-full:before {
+  content: "\f2c7";
+}
+
+.fa-thermometer-half:before {
+  content: "\f2c9";
+}
+
+.fa-thermometer-quarter:before {
+  content: "\f2ca";
+}
+
+.fa-thermometer-three-quarters:before {
+  content: "\f2c8";
+}
+
+.fa-think-peaks:before {
+  content: "\f731";
+}
+
+.fa-thumbs-down:before {
+  content: "\f165";
+}
+
+.fa-thumbs-up:before {
+  content: "\f164";
+}
+
+.fa-thumbtack:before {
+  content: "\f08d";
+}
+
+.fa-ticket-alt:before {
+  content: "\f3ff";
+}
+
+.fa-tiktok:before {
+  content: "\e07b";
+}
+
+.fa-times:before {
+  content: "\f00d";
+}
+
+.fa-times-circle:before {
+  content: "\f057";
+}
+
+.fa-tint:before {
+  content: "\f043";
+}
+
+.fa-tint-slash:before {
+  content: "\f5c7";
+}
+
+.fa-tired:before {
+  content: "\f5c8";
+}
+
+.fa-toggle-off:before {
+  content: "\f204";
+}
+
+.fa-toggle-on:before {
+  content: "\f205";
+}
+
+.fa-toilet:before {
+  content: "\f7d8";
+}
+
+.fa-toilet-paper:before {
+  content: "\f71e";
+}
+
+.fa-toilet-paper-slash:before {
+  content: "\e072";
+}
+
+.fa-toolbox:before {
+  content: "\f552";
+}
+
+.fa-tools:before {
+  content: "\f7d9";
+}
+
+.fa-tooth:before {
+  content: "\f5c9";
+}
+
+.fa-torah:before {
+  content: "\f6a0";
+}
+
+.fa-torii-gate:before {
+  content: "\f6a1";
+}
+
+.fa-tractor:before {
+  content: "\f722";
+}
+
+.fa-trade-federation:before {
+  content: "\f513";
+}
+
+.fa-trademark:before {
+  content: "\f25c";
+}
+
+.fa-traffic-light:before {
+  content: "\f637";
+}
+
+.fa-trailer:before {
+  content: "\e041";
+}
+
+.fa-train:before {
+  content: "\f238";
+}
+
+.fa-tram:before {
+  content: "\f7da";
+}
+
+.fa-transgender:before {
+  content: "\f224";
+}
+
+.fa-transgender-alt:before {
+  content: "\f225";
+}
+
+.fa-trash:before {
+  content: "\f1f8";
+}
+
+.fa-trash-alt:before {
+  content: "\f2ed";
+}
+
+.fa-trash-restore:before {
+  content: "\f829";
+}
+
+.fa-trash-restore-alt:before {
+  content: "\f82a";
+}
+
+.fa-tree:before {
+  content: "\f1bb";
+}
+
+.fa-trello:before {
+  content: "\f181";
+}
+
+.fa-tripadvisor:before {
+  content: "\f262";
+}
+
+.fa-trophy:before {
+  content: "\f091";
+}
+
+.fa-truck:before {
+  content: "\f0d1";
+}
+
+.fa-truck-loading:before {
+  content: "\f4de";
+}
+
+.fa-truck-monster:before {
+  content: "\f63b";
+}
+
+.fa-truck-moving:before {
+  content: "\f4df";
+}
+
+.fa-truck-pickup:before {
+  content: "\f63c";
+}
+
+.fa-tshirt:before {
+  content: "\f553";
+}
+
+.fa-tty:before {
+  content: "\f1e4";
+}
+
+.fa-tumblr:before {
+  content: "\f173";
+}
+
+.fa-tumblr-square:before {
+  content: "\f174";
+}
+
+.fa-tv:before {
+  content: "\f26c";
+}
+
+.fa-twitch:before {
+  content: "\f1e8";
+}
+
+.fa-twitter:before {
+  content: "\f099";
+}
+
+.fa-twitter-square:before {
+  content: "\f081";
+}
+
+.fa-typo3:before {
+  content: "\f42b";
+}
+
+.fa-uber:before {
+  content: "\f402";
+}
+
+.fa-ubuntu:before {
+  content: "\f7df";
+}
+
+.fa-uikit:before {
+  content: "\f403";
+}
+
+.fa-umbraco:before {
+  content: "\f8e8";
+}
+
+.fa-umbrella:before {
+  content: "\f0e9";
+}
+
+.fa-umbrella-beach:before {
+  content: "\f5ca";
+}
+
+.fa-uncharted:before {
+  content: "\e084";
+}
+
+.fa-underline:before {
+  content: "\f0cd";
+}
+
+.fa-undo:before {
+  content: "\f0e2";
+}
+
+.fa-undo-alt:before {
+  content: "\f2ea";
+}
+
+.fa-uniregistry:before {
+  content: "\f404";
+}
+
+.fa-unity:before {
+  content: "\e049";
+}
+
+.fa-universal-access:before {
+  content: "\f29a";
+}
+
+.fa-university:before {
+  content: "\f19c";
+}
+
+.fa-unlink:before {
+  content: "\f127";
+}
+
+.fa-unlock:before {
+  content: "\f09c";
+}
+
+.fa-unlock-alt:before {
+  content: "\f13e";
+}
+
+.fa-unsplash:before {
+  content: "\e07c";
+}
+
+.fa-untappd:before {
+  content: "\f405";
+}
+
+.fa-upload:before {
+  content: "\f093";
+}
+
+.fa-ups:before {
+  content: "\f7e0";
+}
+
+.fa-usb:before {
+  content: "\f287";
+}
+
+.fa-user:before {
+  content: "\f007";
+}
+
+.fa-user-alt:before {
+  content: "\f406";
+}
+
+.fa-user-alt-slash:before {
+  content: "\f4fa";
+}
+
+.fa-user-astronaut:before {
+  content: "\f4fb";
+}
+
+.fa-user-check:before {
+  content: "\f4fc";
+}
+
+.fa-user-circle:before {
+  content: "\f2bd";
+}
+
+.fa-user-clock:before {
+  content: "\f4fd";
+}
+
+.fa-user-cog:before {
+  content: "\f4fe";
+}
+
+.fa-user-edit:before {
+  content: "\f4ff";
+}
+
+.fa-user-friends:before {
+  content: "\f500";
+}
+
+.fa-user-graduate:before {
+  content: "\f501";
+}
+
+.fa-user-injured:before {
+  content: "\f728";
+}
+
+.fa-user-lock:before {
+  content: "\f502";
+}
+
+.fa-user-md:before {
+  content: "\f0f0";
+}
+
+.fa-user-minus:before {
+  content: "\f503";
+}
+
+.fa-user-ninja:before {
+  content: "\f504";
+}
+
+.fa-user-nurse:before {
+  content: "\f82f";
+}
+
+.fa-user-plus:before {
+  content: "\f234";
+}
+
+.fa-user-secret:before {
+  content: "\f21b";
+}
+
+.fa-user-shield:before {
+  content: "\f505";
+}
+
+.fa-user-slash:before {
+  content: "\f506";
+}
+
+.fa-user-tag:before {
+  content: "\f507";
+}
+
+.fa-user-tie:before {
+  content: "\f508";
+}
+
+.fa-user-times:before {
+  content: "\f235";
+}
+
+.fa-users:before {
+  content: "\f0c0";
+}
+
+.fa-users-cog:before {
+  content: "\f509";
+}
+
+.fa-users-slash:before {
+  content: "\e073";
+}
+
+.fa-usps:before {
+  content: "\f7e1";
+}
+
+.fa-ussunnah:before {
+  content: "\f407";
+}
+
+.fa-utensil-spoon:before {
+  content: "\f2e5";
+}
+
+.fa-utensils:before {
+  content: "\f2e7";
+}
+
+.fa-vaadin:before {
+  content: "\f408";
+}
+
+.fa-vector-square:before {
+  content: "\f5cb";
+}
+
+.fa-venus:before {
+  content: "\f221";
+}
+
+.fa-venus-double:before {
+  content: "\f226";
+}
+
+.fa-venus-mars:before {
+  content: "\f228";
+}
+
+.fa-vest:before {
+  content: "\e085";
+}
+
+.fa-vest-patches:before {
+  content: "\e086";
+}
+
+.fa-viacoin:before {
+  content: "\f237";
+}
+
+.fa-viadeo:before {
+  content: "\f2a9";
+}
+
+.fa-viadeo-square:before {
+  content: "\f2aa";
+}
+
+.fa-vial:before {
+  content: "\f492";
+}
+
+.fa-vials:before {
+  content: "\f493";
+}
+
+.fa-viber:before {
+  content: "\f409";
+}
+
+.fa-video:before {
+  content: "\f03d";
+}
+
+.fa-video-slash:before {
+  content: "\f4e2";
+}
+
+.fa-vihara:before {
+  content: "\f6a7";
+}
+
+.fa-vimeo:before {
+  content: "\f40a";
+}
+
+.fa-vimeo-square:before {
+  content: "\f194";
+}
+
+.fa-vimeo-v:before {
+  content: "\f27d";
+}
+
+.fa-vine:before {
+  content: "\f1ca";
+}
+
+.fa-virus:before {
+  content: "\e074";
+}
+
+.fa-virus-slash:before {
+  content: "\e075";
+}
+
+.fa-viruses:before {
+  content: "\e076";
+}
+
+.fa-vk:before {
+  content: "\f189";
+}
+
+.fa-vnv:before {
+  content: "\f40b";
+}
+
+.fa-voicemail:before {
+  content: "\f897";
+}
+
+.fa-volleyball-ball:before {
+  content: "\f45f";
+}
+
+.fa-volume-down:before {
+  content: "\f027";
+}
+
+.fa-volume-mute:before {
+  content: "\f6a9";
+}
+
+.fa-volume-off:before {
+  content: "\f026";
+}
+
+.fa-volume-up:before {
+  content: "\f028";
+}
+
+.fa-vote-yea:before {
+  content: "\f772";
+}
+
+.fa-vr-cardboard:before {
+  content: "\f729";
+}
+
+.fa-vuejs:before {
+  content: "\f41f";
+}
+
+.fa-walking:before {
+  content: "\f554";
+}
+
+.fa-wallet:before {
+  content: "\f555";
+}
+
+.fa-warehouse:before {
+  content: "\f494";
+}
+
+.fa-watchman-monitoring:before {
+  content: "\e087";
+}
+
+.fa-water:before {
+  content: "\f773";
+}
+
+.fa-wave-square:before {
+  content: "\f83e";
+}
+
+.fa-waze:before {
+  content: "\f83f";
+}
+
+.fa-weebly:before {
+  content: "\f5cc";
+}
+
+.fa-weibo:before {
+  content: "\f18a";
+}
+
+.fa-weight:before {
+  content: "\f496";
+}
+
+.fa-weight-hanging:before {
+  content: "\f5cd";
+}
+
+.fa-weixin:before {
+  content: "\f1d7";
+}
+
+.fa-whatsapp:before {
+  content: "\f232";
+}
+
+.fa-whatsapp-square:before {
+  content: "\f40c";
+}
+
+.fa-wheelchair:before {
+  content: "\f193";
+}
+
+.fa-whmcs:before {
+  content: "\f40d";
+}
+
+.fa-wifi:before {
+  content: "\f1eb";
+}
+
+.fa-wikipedia-w:before {
+  content: "\f266";
+}
+
+.fa-wind:before {
+  content: "\f72e";
+}
+
+.fa-window-close:before {
+  content: "\f410";
+}
+
+.fa-window-maximize:before {
+  content: "\f2d0";
+}
+
+.fa-window-minimize:before {
+  content: "\f2d1";
+}
+
+.fa-window-restore:before {
+  content: "\f2d2";
+}
+
+.fa-windows:before {
+  content: "\f17a";
+}
+
+.fa-wine-bottle:before {
+  content: "\f72f";
+}
+
+.fa-wine-glass:before {
+  content: "\f4e3";
+}
+
+.fa-wine-glass-alt:before {
+  content: "\f5ce";
+}
+
+.fa-wix:before {
+  content: "\f5cf";
+}
+
+.fa-wizards-of-the-coast:before {
+  content: "\f730";
+}
+
+.fa-wodu:before {
+  content: "\e088";
+}
+
+.fa-wolf-pack-battalion:before {
+  content: "\f514";
+}
+
+.fa-won-sign:before {
+  content: "\f159";
+}
+
+.fa-wordpress:before {
+  content: "\f19a";
+}
+
+.fa-wordpress-simple:before {
+  content: "\f411";
+}
+
+.fa-wpbeginner:before {
+  content: "\f297";
+}
+
+.fa-wpexplorer:before {
+  content: "\f2de";
+}
+
+.fa-wpforms:before {
+  content: "\f298";
+}
+
+.fa-wpressr:before {
+  content: "\f3e4";
+}
+
+.fa-wrench:before {
+  content: "\f0ad";
+}
+
+.fa-x-ray:before {
+  content: "\f497";
+}
+
+.fa-xbox:before {
+  content: "\f412";
+}
+
+.fa-xing:before {
+  content: "\f168";
+}
+
+.fa-xing-square:before {
+  content: "\f169";
+}
+
+.fa-y-combinator:before {
+  content: "\f23b";
+}
+
+.fa-yahoo:before {
+  content: "\f19e";
+}
+
+.fa-yammer:before {
+  content: "\f840";
+}
+
+.fa-yandex:before {
+  content: "\f413";
+}
+
+.fa-yandex-international:before {
+  content: "\f414";
+}
+
+.fa-yarn:before {
+  content: "\f7e3";
+}
+
+.fa-yelp:before {
+  content: "\f1e9";
+}
+
+.fa-yen-sign:before {
+  content: "\f157";
+}
+
+.fa-yin-yang:before {
+  content: "\f6ad";
+}
+
+.fa-yoast:before {
+  content: "\f2b1";
+}
+
+.fa-youtube:before {
+  content: "\f167";
+}
+
+.fa-youtube-square:before {
+  content: "\f431";
+}
+
+.fa-zhihu:before {
+  content: "\f63f";
+}
+
+.sr-only {
+  border: 0;
+  clip: rect(0, 0, 0, 0);
+  height: 1px;
+  margin: -1px;
+  overflow: hidden;
+  padding: 0;
+  position: absolute;
+  width: 1px;
+}
+
+.sr-only-focusable:active, .sr-only-focusable:focus {
+  clip: auto;
+  height: auto;
+  margin: 0;
+  overflow: visible;
+  position: static;
+  width: auto;
+}
+/*# sourceMappingURL=fontawesome.css.map */

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 0
vendor/fontawesome-free/scss/fontawesome.css.map


+ 18 - 0
vendor/fontawesome-free/scss/regular.css

@@ -0,0 +1,18 @@
+/*!
+ * Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face {
+  font-family: 'Font Awesome 5 Free';
+  font-style: normal;
+  font-weight: 400;
+  font-display: block;
+  src: url("../webfonts/fa-regular-400.eot");
+  src: url("../webfonts/fa-regular-400.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-regular-400.woff2") format("woff2"), url("../webfonts/fa-regular-400.woff") format("woff"), url("../webfonts/fa-regular-400.ttf") format("truetype"), url("../webfonts/fa-regular-400.svg#fontawesome") format("svg");
+}
+
+.far {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+/*# sourceMappingURL=regular.css.map */

+ 10 - 0
vendor/fontawesome-free/scss/regular.css.map

@@ -0,0 +1,10 @@
+{
+    "version": 3,
+    "mappings": "AAAA;;;GAGG;AAGH,UAAU;EACR,WAAW,EAAE,qBAAqB;EAClC,UAAU,EAAE,MAAM;EAClB,WAAW,EAAE,GAAG;EAChB,YAAY,ECLS,KAAK;EDM1B,GAAG,EAAE,qCAA0C;EAC/C,GAAG,EAAE,4CAAiD,CAAC,2BAA2B,EAClF,uCAA4C,CAAC,eAAe,EAC5D,sCAA2C,CAAC,cAAc,EAC1D,qCAA0C,CAAC,kBAAkB,EAC7D,iDAAsD,CAAC,aAAa;;;AAGtE,AAAA,IAAI,CAAC;EACH,WAAW,EAAE,qBAAqB;EAClC,WAAW,EAAE,GAAG;CACjB",
+    "sources": [
+        "regular.scss",
+        "_variables.scss"
+    ],
+    "names": [],
+    "file": "regular.css"
+}

+ 19 - 0
vendor/fontawesome-free/scss/solid.css

@@ -0,0 +1,19 @@
+/*!
+ * Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+@font-face {
+  font-family: 'Font Awesome 5 Free';
+  font-style: normal;
+  font-weight: 900;
+  font-display: block;
+  src: url("../webfonts/fa-solid-900.eot");
+  src: url("../webfonts/fa-solid-900.eot?#iefix") format("embedded-opentype"), url("../webfonts/fa-solid-900.woff2") format("woff2"), url("../webfonts/fa-solid-900.woff") format("woff"), url("../webfonts/fa-solid-900.ttf") format("truetype"), url("../webfonts/fa-solid-900.svg#fontawesome") format("svg");
+}
+
+.fa,
+.fas {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 900;
+}
+/*# sourceMappingURL=solid.css.map */

+ 10 - 0
vendor/fontawesome-free/scss/solid.css.map

@@ -0,0 +1,10 @@
+{
+    "version": 3,
+    "mappings": "AAAA;;;GAGG;AAGH,UAAU;EACR,WAAW,EAAE,qBAAqB;EAClC,UAAU,EAAE,MAAM;EAClB,WAAW,EAAE,GAAG;EAChB,YAAY,ECLS,KAAK;EDM1B,GAAG,EAAE,mCAAwC;EAC7C,GAAG,EAAE,0CAA+C,CAAC,2BAA2B,EAChF,qCAA0C,CAAC,eAAe,EAC1D,oCAAyC,CAAC,cAAc,EACxD,mCAAwC,CAAC,kBAAkB,EAC3D,+CAAoD,CAAC,aAAa;;;AAGpE,AAAA,GAAG;AACH,IAAI,CAAC;EACH,WAAW,EAAE,qBAAqB;EAClC,WAAW,EAAE,GAAG;CACjB",
+    "sources": [
+        "solid.scss",
+        "_variables.scss"
+    ],
+    "names": [],
+    "file": "solid.css"
+}

+ 2786 - 0
vendor/fontawesome-free/scss/v4-shims.css

@@ -0,0 +1,2786 @@
+/*!
+ * Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com
+ * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
+ */
+.fa.fa-glass:before {
+  content: "\f000";
+}
+
+.fa.fa-meetup {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-star-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-star-o:before {
+  content: "\f005";
+}
+
+.fa.fa-remove:before {
+  content: "\f00d";
+}
+
+.fa.fa-close:before {
+  content: "\f00d";
+}
+
+.fa.fa-gear:before {
+  content: "\f013";
+}
+
+.fa.fa-trash-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-trash-o:before {
+  content: "\f2ed";
+}
+
+.fa.fa-file-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-o:before {
+  content: "\f15b";
+}
+
+.fa.fa-clock-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-clock-o:before {
+  content: "\f017";
+}
+
+.fa.fa-arrow-circle-o-down {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-arrow-circle-o-down:before {
+  content: "\f358";
+}
+
+.fa.fa-arrow-circle-o-up {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-arrow-circle-o-up:before {
+  content: "\f35b";
+}
+
+.fa.fa-play-circle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-play-circle-o:before {
+  content: "\f144";
+}
+
+.fa.fa-repeat:before {
+  content: "\f01e";
+}
+
+.fa.fa-rotate-right:before {
+  content: "\f01e";
+}
+
+.fa.fa-refresh:before {
+  content: "\f021";
+}
+
+.fa.fa-list-alt {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-dedent:before {
+  content: "\f03b";
+}
+
+.fa.fa-video-camera:before {
+  content: "\f03d";
+}
+
+.fa.fa-picture-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-picture-o:before {
+  content: "\f03e";
+}
+
+.fa.fa-photo {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-photo:before {
+  content: "\f03e";
+}
+
+.fa.fa-image {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-image:before {
+  content: "\f03e";
+}
+
+.fa.fa-pencil:before {
+  content: "\f303";
+}
+
+.fa.fa-map-marker:before {
+  content: "\f3c5";
+}
+
+.fa.fa-pencil-square-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-pencil-square-o:before {
+  content: "\f044";
+}
+
+.fa.fa-share-square-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-share-square-o:before {
+  content: "\f14d";
+}
+
+.fa.fa-check-square-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-check-square-o:before {
+  content: "\f14a";
+}
+
+.fa.fa-arrows:before {
+  content: "\f0b2";
+}
+
+.fa.fa-times-circle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-times-circle-o:before {
+  content: "\f057";
+}
+
+.fa.fa-check-circle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-check-circle-o:before {
+  content: "\f058";
+}
+
+.fa.fa-mail-forward:before {
+  content: "\f064";
+}
+
+.fa.fa-expand:before {
+  content: "\f424";
+}
+
+.fa.fa-compress:before {
+  content: "\f422";
+}
+
+.fa.fa-eye {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-eye-slash {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-warning:before {
+  content: "\f071";
+}
+
+.fa.fa-calendar:before {
+  content: "\f073";
+}
+
+.fa.fa-arrows-v:before {
+  content: "\f338";
+}
+
+.fa.fa-arrows-h:before {
+  content: "\f337";
+}
+
+.fa.fa-bar-chart {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-bar-chart:before {
+  content: "\f080";
+}
+
+.fa.fa-bar-chart-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-bar-chart-o:before {
+  content: "\f080";
+}
+
+.fa.fa-twitter-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-facebook-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-gears:before {
+  content: "\f085";
+}
+
+.fa.fa-thumbs-o-up {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-thumbs-o-up:before {
+  content: "\f164";
+}
+
+.fa.fa-thumbs-o-down {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-thumbs-o-down:before {
+  content: "\f165";
+}
+
+.fa.fa-heart-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-heart-o:before {
+  content: "\f004";
+}
+
+.fa.fa-sign-out:before {
+  content: "\f2f5";
+}
+
+.fa.fa-linkedin-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-linkedin-square:before {
+  content: "\f08c";
+}
+
+.fa.fa-thumb-tack:before {
+  content: "\f08d";
+}
+
+.fa.fa-external-link:before {
+  content: "\f35d";
+}
+
+.fa.fa-sign-in:before {
+  content: "\f2f6";
+}
+
+.fa.fa-github-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-lemon-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-lemon-o:before {
+  content: "\f094";
+}
+
+.fa.fa-square-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-square-o:before {
+  content: "\f0c8";
+}
+
+.fa.fa-bookmark-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-bookmark-o:before {
+  content: "\f02e";
+}
+
+.fa.fa-twitter {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-facebook {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-facebook:before {
+  content: "\f39e";
+}
+
+.fa.fa-facebook-f {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-facebook-f:before {
+  content: "\f39e";
+}
+
+.fa.fa-github {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-credit-card {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-feed:before {
+  content: "\f09e";
+}
+
+.fa.fa-hdd-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hdd-o:before {
+  content: "\f0a0";
+}
+
+.fa.fa-hand-o-right {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-o-right:before {
+  content: "\f0a4";
+}
+
+.fa.fa-hand-o-left {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-o-left:before {
+  content: "\f0a5";
+}
+
+.fa.fa-hand-o-up {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-o-up:before {
+  content: "\f0a6";
+}
+
+.fa.fa-hand-o-down {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-o-down:before {
+  content: "\f0a7";
+}
+
+.fa.fa-arrows-alt:before {
+  content: "\f31e";
+}
+
+.fa.fa-group:before {
+  content: "\f0c0";
+}
+
+.fa.fa-chain:before {
+  content: "\f0c1";
+}
+
+.fa.fa-scissors:before {
+  content: "\f0c4";
+}
+
+.fa.fa-files-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-files-o:before {
+  content: "\f0c5";
+}
+
+.fa.fa-floppy-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-floppy-o:before {
+  content: "\f0c7";
+}
+
+.fa.fa-navicon:before {
+  content: "\f0c9";
+}
+
+.fa.fa-reorder:before {
+  content: "\f0c9";
+}
+
+.fa.fa-pinterest {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-pinterest-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-google-plus-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-google-plus {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-google-plus:before {
+  content: "\f0d5";
+}
+
+.fa.fa-money {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-money:before {
+  content: "\f3d1";
+}
+
+.fa.fa-unsorted:before {
+  content: "\f0dc";
+}
+
+.fa.fa-sort-desc:before {
+  content: "\f0dd";
+}
+
+.fa.fa-sort-asc:before {
+  content: "\f0de";
+}
+
+.fa.fa-linkedin {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-linkedin:before {
+  content: "\f0e1";
+}
+
+.fa.fa-rotate-left:before {
+  content: "\f0e2";
+}
+
+.fa.fa-legal:before {
+  content: "\f0e3";
+}
+
+.fa.fa-tachometer:before {
+  content: "\f3fd";
+}
+
+.fa.fa-dashboard:before {
+  content: "\f3fd";
+}
+
+.fa.fa-comment-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-comment-o:before {
+  content: "\f075";
+}
+
+.fa.fa-comments-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-comments-o:before {
+  content: "\f086";
+}
+
+.fa.fa-flash:before {
+  content: "\f0e7";
+}
+
+.fa.fa-clipboard {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-paste {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-paste:before {
+  content: "\f328";
+}
+
+.fa.fa-lightbulb-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-lightbulb-o:before {
+  content: "\f0eb";
+}
+
+.fa.fa-exchange:before {
+  content: "\f362";
+}
+
+.fa.fa-cloud-download:before {
+  content: "\f381";
+}
+
+.fa.fa-cloud-upload:before {
+  content: "\f382";
+}
+
+.fa.fa-bell-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-bell-o:before {
+  content: "\f0f3";
+}
+
+.fa.fa-cutlery:before {
+  content: "\f2e7";
+}
+
+.fa.fa-file-text-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-text-o:before {
+  content: "\f15c";
+}
+
+.fa.fa-building-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-building-o:before {
+  content: "\f1ad";
+}
+
+.fa.fa-hospital-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hospital-o:before {
+  content: "\f0f8";
+}
+
+.fa.fa-tablet:before {
+  content: "\f3fa";
+}
+
+.fa.fa-mobile:before {
+  content: "\f3cd";
+}
+
+.fa.fa-mobile-phone:before {
+  content: "\f3cd";
+}
+
+.fa.fa-circle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-circle-o:before {
+  content: "\f111";
+}
+
+.fa.fa-mail-reply:before {
+  content: "\f3e5";
+}
+
+.fa.fa-github-alt {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-folder-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-folder-o:before {
+  content: "\f07b";
+}
+
+.fa.fa-folder-open-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-folder-open-o:before {
+  content: "\f07c";
+}
+
+.fa.fa-smile-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-smile-o:before {
+  content: "\f118";
+}
+
+.fa.fa-frown-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-frown-o:before {
+  content: "\f119";
+}
+
+.fa.fa-meh-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-meh-o:before {
+  content: "\f11a";
+}
+
+.fa.fa-keyboard-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-keyboard-o:before {
+  content: "\f11c";
+}
+
+.fa.fa-flag-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-flag-o:before {
+  content: "\f024";
+}
+
+.fa.fa-mail-reply-all:before {
+  content: "\f122";
+}
+
+.fa.fa-star-half-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-star-half-o:before {
+  content: "\f089";
+}
+
+.fa.fa-star-half-empty {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-star-half-empty:before {
+  content: "\f089";
+}
+
+.fa.fa-star-half-full {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-star-half-full:before {
+  content: "\f089";
+}
+
+.fa.fa-code-fork:before {
+  content: "\f126";
+}
+
+.fa.fa-chain-broken:before {
+  content: "\f127";
+}
+
+.fa.fa-shield:before {
+  content: "\f3ed";
+}
+
+.fa.fa-calendar-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-calendar-o:before {
+  content: "\f133";
+}
+
+.fa.fa-maxcdn {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-html5 {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-css3 {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-ticket:before {
+  content: "\f3ff";
+}
+
+.fa.fa-minus-square-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-minus-square-o:before {
+  content: "\f146";
+}
+
+.fa.fa-level-up:before {
+  content: "\f3bf";
+}
+
+.fa.fa-level-down:before {
+  content: "\f3be";
+}
+
+.fa.fa-pencil-square:before {
+  content: "\f14b";
+}
+
+.fa.fa-external-link-square:before {
+  content: "\f360";
+}
+
+.fa.fa-compass {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-caret-square-o-down {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-caret-square-o-down:before {
+  content: "\f150";
+}
+
+.fa.fa-toggle-down {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-toggle-down:before {
+  content: "\f150";
+}
+
+.fa.fa-caret-square-o-up {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-caret-square-o-up:before {
+  content: "\f151";
+}
+
+.fa.fa-toggle-up {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-toggle-up:before {
+  content: "\f151";
+}
+
+.fa.fa-caret-square-o-right {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-caret-square-o-right:before {
+  content: "\f152";
+}
+
+.fa.fa-toggle-right {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-toggle-right:before {
+  content: "\f152";
+}
+
+.fa.fa-eur:before {
+  content: "\f153";
+}
+
+.fa.fa-euro:before {
+  content: "\f153";
+}
+
+.fa.fa-gbp:before {
+  content: "\f154";
+}
+
+.fa.fa-usd:before {
+  content: "\f155";
+}
+
+.fa.fa-dollar:before {
+  content: "\f155";
+}
+
+.fa.fa-inr:before {
+  content: "\f156";
+}
+
+.fa.fa-rupee:before {
+  content: "\f156";
+}
+
+.fa.fa-jpy:before {
+  content: "\f157";
+}
+
+.fa.fa-cny:before {
+  content: "\f157";
+}
+
+.fa.fa-rmb:before {
+  content: "\f157";
+}
+
+.fa.fa-yen:before {
+  content: "\f157";
+}
+
+.fa.fa-rub:before {
+  content: "\f158";
+}
+
+.fa.fa-ruble:before {
+  content: "\f158";
+}
+
+.fa.fa-rouble:before {
+  content: "\f158";
+}
+
+.fa.fa-krw:before {
+  content: "\f159";
+}
+
+.fa.fa-won:before {
+  content: "\f159";
+}
+
+.fa.fa-btc {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-bitcoin {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-bitcoin:before {
+  content: "\f15a";
+}
+
+.fa.fa-file-text:before {
+  content: "\f15c";
+}
+
+.fa.fa-sort-alpha-asc:before {
+  content: "\f15d";
+}
+
+.fa.fa-sort-alpha-desc:before {
+  content: "\f881";
+}
+
+.fa.fa-sort-amount-asc:before {
+  content: "\f160";
+}
+
+.fa.fa-sort-amount-desc:before {
+  content: "\f884";
+}
+
+.fa.fa-sort-numeric-asc:before {
+  content: "\f162";
+}
+
+.fa.fa-sort-numeric-desc:before {
+  content: "\f886";
+}
+
+.fa.fa-youtube-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-youtube {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-xing {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-xing-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-youtube-play {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-youtube-play:before {
+  content: "\f167";
+}
+
+.fa.fa-dropbox {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-stack-overflow {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-instagram {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-flickr {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-adn {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-bitbucket {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-bitbucket-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-bitbucket-square:before {
+  content: "\f171";
+}
+
+.fa.fa-tumblr {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-tumblr-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-long-arrow-down:before {
+  content: "\f309";
+}
+
+.fa.fa-long-arrow-up:before {
+  content: "\f30c";
+}
+
+.fa.fa-long-arrow-left:before {
+  content: "\f30a";
+}
+
+.fa.fa-long-arrow-right:before {
+  content: "\f30b";
+}
+
+.fa.fa-apple {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-windows {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-android {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-linux {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-dribbble {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-skype {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-foursquare {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-trello {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-gratipay {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-gittip {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-gittip:before {
+  content: "\f184";
+}
+
+.fa.fa-sun-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-sun-o:before {
+  content: "\f185";
+}
+
+.fa.fa-moon-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-moon-o:before {
+  content: "\f186";
+}
+
+.fa.fa-vk {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-weibo {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-renren {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-pagelines {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-stack-exchange {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-arrow-circle-o-right {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-arrow-circle-o-right:before {
+  content: "\f35a";
+}
+
+.fa.fa-arrow-circle-o-left {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-arrow-circle-o-left:before {
+  content: "\f359";
+}
+
+.fa.fa-caret-square-o-left {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-caret-square-o-left:before {
+  content: "\f191";
+}
+
+.fa.fa-toggle-left {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-toggle-left:before {
+  content: "\f191";
+}
+
+.fa.fa-dot-circle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-dot-circle-o:before {
+  content: "\f192";
+}
+
+.fa.fa-vimeo-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-try:before {
+  content: "\f195";
+}
+
+.fa.fa-turkish-lira:before {
+  content: "\f195";
+}
+
+.fa.fa-plus-square-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-plus-square-o:before {
+  content: "\f0fe";
+}
+
+.fa.fa-slack {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-wordpress {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-openid {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-institution:before {
+  content: "\f19c";
+}
+
+.fa.fa-bank:before {
+  content: "\f19c";
+}
+
+.fa.fa-mortar-board:before {
+  content: "\f19d";
+}
+
+.fa.fa-yahoo {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-google {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-reddit {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-reddit-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-stumbleupon-circle {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-stumbleupon {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-delicious {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-digg {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-pied-piper-pp {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-pied-piper-alt {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-drupal {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-joomla {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-spoon:before {
+  content: "\f2e5";
+}
+
+.fa.fa-behance {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-behance-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-steam {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-steam-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-automobile:before {
+  content: "\f1b9";
+}
+
+.fa.fa-envelope-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-envelope-o:before {
+  content: "\f0e0";
+}
+
+.fa.fa-spotify {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-deviantart {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-soundcloud {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-file-pdf-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-pdf-o:before {
+  content: "\f1c1";
+}
+
+.fa.fa-file-word-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-word-o:before {
+  content: "\f1c2";
+}
+
+.fa.fa-file-excel-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-excel-o:before {
+  content: "\f1c3";
+}
+
+.fa.fa-file-powerpoint-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-powerpoint-o:before {
+  content: "\f1c4";
+}
+
+.fa.fa-file-image-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-image-o:before {
+  content: "\f1c5";
+}
+
+.fa.fa-file-photo-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-photo-o:before {
+  content: "\f1c5";
+}
+
+.fa.fa-file-picture-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-picture-o:before {
+  content: "\f1c5";
+}
+
+.fa.fa-file-archive-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-archive-o:before {
+  content: "\f1c6";
+}
+
+.fa.fa-file-zip-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-zip-o:before {
+  content: "\f1c6";
+}
+
+.fa.fa-file-audio-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-audio-o:before {
+  content: "\f1c7";
+}
+
+.fa.fa-file-sound-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-sound-o:before {
+  content: "\f1c7";
+}
+
+.fa.fa-file-video-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-video-o:before {
+  content: "\f1c8";
+}
+
+.fa.fa-file-movie-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-movie-o:before {
+  content: "\f1c8";
+}
+
+.fa.fa-file-code-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-file-code-o:before {
+  content: "\f1c9";
+}
+
+.fa.fa-vine {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-codepen {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-jsfiddle {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-life-ring {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-life-bouy {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-life-bouy:before {
+  content: "\f1cd";
+}
+
+.fa.fa-life-buoy {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-life-buoy:before {
+  content: "\f1cd";
+}
+
+.fa.fa-life-saver {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-life-saver:before {
+  content: "\f1cd";
+}
+
+.fa.fa-support {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-support:before {
+  content: "\f1cd";
+}
+
+.fa.fa-circle-o-notch:before {
+  content: "\f1ce";
+}
+
+.fa.fa-rebel {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-ra {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-ra:before {
+  content: "\f1d0";
+}
+
+.fa.fa-resistance {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-resistance:before {
+  content: "\f1d0";
+}
+
+.fa.fa-empire {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-ge {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-ge:before {
+  content: "\f1d1";
+}
+
+.fa.fa-git-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-git {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-hacker-news {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-y-combinator-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-y-combinator-square:before {
+  content: "\f1d4";
+}
+
+.fa.fa-yc-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-yc-square:before {
+  content: "\f1d4";
+}
+
+.fa.fa-tencent-weibo {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-qq {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-weixin {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-wechat {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-wechat:before {
+  content: "\f1d7";
+}
+
+.fa.fa-send:before {
+  content: "\f1d8";
+}
+
+.fa.fa-paper-plane-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-paper-plane-o:before {
+  content: "\f1d8";
+}
+
+.fa.fa-send-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-send-o:before {
+  content: "\f1d8";
+}
+
+.fa.fa-circle-thin {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-circle-thin:before {
+  content: "\f111";
+}
+
+.fa.fa-header:before {
+  content: "\f1dc";
+}
+
+.fa.fa-sliders:before {
+  content: "\f1de";
+}
+
+.fa.fa-futbol-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-futbol-o:before {
+  content: "\f1e3";
+}
+
+.fa.fa-soccer-ball-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-soccer-ball-o:before {
+  content: "\f1e3";
+}
+
+.fa.fa-slideshare {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-twitch {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-yelp {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-newspaper-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-newspaper-o:before {
+  content: "\f1ea";
+}
+
+.fa.fa-paypal {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-google-wallet {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-cc-visa {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-cc-mastercard {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-cc-discover {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-cc-amex {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-cc-paypal {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-cc-stripe {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-bell-slash-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-bell-slash-o:before {
+  content: "\f1f6";
+}
+
+.fa.fa-trash:before {
+  content: "\f2ed";
+}
+
+.fa.fa-copyright {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-eyedropper:before {
+  content: "\f1fb";
+}
+
+.fa.fa-area-chart:before {
+  content: "\f1fe";
+}
+
+.fa.fa-pie-chart:before {
+  content: "\f200";
+}
+
+.fa.fa-line-chart:before {
+  content: "\f201";
+}
+
+.fa.fa-lastfm {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-lastfm-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-ioxhost {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-angellist {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-cc {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-cc:before {
+  content: "\f20a";
+}
+
+.fa.fa-ils:before {
+  content: "\f20b";
+}
+
+.fa.fa-shekel:before {
+  content: "\f20b";
+}
+
+.fa.fa-sheqel:before {
+  content: "\f20b";
+}
+
+.fa.fa-meanpath {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-meanpath:before {
+  content: "\f2b4";
+}
+
+.fa.fa-buysellads {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-connectdevelop {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-dashcube {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-forumbee {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-leanpub {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-sellsy {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-shirtsinbulk {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-simplybuilt {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-skyatlas {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-diamond {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-diamond:before {
+  content: "\f3a5";
+}
+
+.fa.fa-intersex:before {
+  content: "\f224";
+}
+
+.fa.fa-facebook-official {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-facebook-official:before {
+  content: "\f09a";
+}
+
+.fa.fa-pinterest-p {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-whatsapp {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-hotel:before {
+  content: "\f236";
+}
+
+.fa.fa-viacoin {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-medium {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-y-combinator {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-yc {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-yc:before {
+  content: "\f23b";
+}
+
+.fa.fa-optin-monster {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-opencart {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-expeditedssl {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-battery-4:before {
+  content: "\f240";
+}
+
+.fa.fa-battery:before {
+  content: "\f240";
+}
+
+.fa.fa-battery-3:before {
+  content: "\f241";
+}
+
+.fa.fa-battery-2:before {
+  content: "\f242";
+}
+
+.fa.fa-battery-1:before {
+  content: "\f243";
+}
+
+.fa.fa-battery-0:before {
+  content: "\f244";
+}
+
+.fa.fa-object-group {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-object-ungroup {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-sticky-note-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-sticky-note-o:before {
+  content: "\f249";
+}
+
+.fa.fa-cc-jcb {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-cc-diners-club {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-clone {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hourglass-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hourglass-o:before {
+  content: "\f254";
+}
+
+.fa.fa-hourglass-1:before {
+  content: "\f251";
+}
+
+.fa.fa-hourglass-2:before {
+  content: "\f252";
+}
+
+.fa.fa-hourglass-3:before {
+  content: "\f253";
+}
+
+.fa.fa-hand-rock-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-rock-o:before {
+  content: "\f255";
+}
+
+.fa.fa-hand-grab-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-grab-o:before {
+  content: "\f255";
+}
+
+.fa.fa-hand-paper-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-paper-o:before {
+  content: "\f256";
+}
+
+.fa.fa-hand-stop-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-stop-o:before {
+  content: "\f256";
+}
+
+.fa.fa-hand-scissors-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-scissors-o:before {
+  content: "\f257";
+}
+
+.fa.fa-hand-lizard-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-lizard-o:before {
+  content: "\f258";
+}
+
+.fa.fa-hand-spock-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-spock-o:before {
+  content: "\f259";
+}
+
+.fa.fa-hand-pointer-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-pointer-o:before {
+  content: "\f25a";
+}
+
+.fa.fa-hand-peace-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-hand-peace-o:before {
+  content: "\f25b";
+}
+
+.fa.fa-registered {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-creative-commons {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-gg {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-gg-circle {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-tripadvisor {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-odnoklassniki {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-odnoklassniki-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-get-pocket {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-wikipedia-w {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-safari {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-chrome {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-firefox {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-opera {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-internet-explorer {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-television:before {
+  content: "\f26c";
+}
+
+.fa.fa-contao {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-500px {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-amazon {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-calendar-plus-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-calendar-plus-o:before {
+  content: "\f271";
+}
+
+.fa.fa-calendar-minus-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-calendar-minus-o:before {
+  content: "\f272";
+}
+
+.fa.fa-calendar-times-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-calendar-times-o:before {
+  content: "\f273";
+}
+
+.fa.fa-calendar-check-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-calendar-check-o:before {
+  content: "\f274";
+}
+
+.fa.fa-map-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-map-o:before {
+  content: "\f279";
+}
+
+.fa.fa-commenting:before {
+  content: "\f4ad";
+}
+
+.fa.fa-commenting-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-commenting-o:before {
+  content: "\f4ad";
+}
+
+.fa.fa-houzz {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-vimeo {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-vimeo:before {
+  content: "\f27d";
+}
+
+.fa.fa-black-tie {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-fonticons {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-reddit-alien {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-edge {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-credit-card-alt:before {
+  content: "\f09d";
+}
+
+.fa.fa-codiepie {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-modx {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-fort-awesome {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-usb {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-product-hunt {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-mixcloud {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-scribd {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-pause-circle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-pause-circle-o:before {
+  content: "\f28b";
+}
+
+.fa.fa-stop-circle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-stop-circle-o:before {
+  content: "\f28d";
+}
+
+.fa.fa-bluetooth {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-bluetooth-b {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-gitlab {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-wpbeginner {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-wpforms {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-envira {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-wheelchair-alt {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-wheelchair-alt:before {
+  content: "\f368";
+}
+
+.fa.fa-question-circle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-question-circle-o:before {
+  content: "\f059";
+}
+
+.fa.fa-volume-control-phone:before {
+  content: "\f2a0";
+}
+
+.fa.fa-asl-interpreting:before {
+  content: "\f2a3";
+}
+
+.fa.fa-deafness:before {
+  content: "\f2a4";
+}
+
+.fa.fa-hard-of-hearing:before {
+  content: "\f2a4";
+}
+
+.fa.fa-glide {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-glide-g {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-signing:before {
+  content: "\f2a7";
+}
+
+.fa.fa-viadeo {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-viadeo-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-snapchat {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-snapchat-ghost {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-snapchat-square {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-pied-piper {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-first-order {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-yoast {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-themeisle {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-google-plus-official {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-google-plus-official:before {
+  content: "\f2b3";
+}
+
+.fa.fa-google-plus-circle {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-google-plus-circle:before {
+  content: "\f2b3";
+}
+
+.fa.fa-font-awesome {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-fa {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-fa:before {
+  content: "\f2b4";
+}
+
+.fa.fa-handshake-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-handshake-o:before {
+  content: "\f2b5";
+}
+
+.fa.fa-envelope-open-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-envelope-open-o:before {
+  content: "\f2b6";
+}
+
+.fa.fa-linode {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-address-book-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-address-book-o:before {
+  content: "\f2b9";
+}
+
+.fa.fa-vcard:before {
+  content: "\f2bb";
+}
+
+.fa.fa-address-card-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-address-card-o:before {
+  content: "\f2bb";
+}
+
+.fa.fa-vcard-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-vcard-o:before {
+  content: "\f2bb";
+}
+
+.fa.fa-user-circle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-user-circle-o:before {
+  content: "\f2bd";
+}
+
+.fa.fa-user-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-user-o:before {
+  content: "\f007";
+}
+
+.fa.fa-id-badge {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-drivers-license:before {
+  content: "\f2c2";
+}
+
+.fa.fa-id-card-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-id-card-o:before {
+  content: "\f2c2";
+}
+
+.fa.fa-drivers-license-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-drivers-license-o:before {
+  content: "\f2c2";
+}
+
+.fa.fa-quora {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-free-code-camp {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-telegram {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-thermometer-4:before {
+  content: "\f2c7";
+}
+
+.fa.fa-thermometer:before {
+  content: "\f2c7";
+}
+
+.fa.fa-thermometer-3:before {
+  content: "\f2c8";
+}
+
+.fa.fa-thermometer-2:before {
+  content: "\f2c9";
+}
+
+.fa.fa-thermometer-1:before {
+  content: "\f2ca";
+}
+
+.fa.fa-thermometer-0:before {
+  content: "\f2cb";
+}
+
+.fa.fa-bathtub:before {
+  content: "\f2cd";
+}
+
+.fa.fa-s15:before {
+  content: "\f2cd";
+}
+
+.fa.fa-window-maximize {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-window-restore {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-times-rectangle:before {
+  content: "\f410";
+}
+
+.fa.fa-window-close-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-window-close-o:before {
+  content: "\f410";
+}
+
+.fa.fa-times-rectangle-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-times-rectangle-o:before {
+  content: "\f410";
+}
+
+.fa.fa-bandcamp {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-grav {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-etsy {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-imdb {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-ravelry {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-eercast {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-eercast:before {
+  content: "\f2da";
+}
+
+.fa.fa-snowflake-o {
+  font-family: 'Font Awesome 5 Free';
+  font-weight: 400;
+}
+
+.fa.fa-snowflake-o:before {
+  content: "\f2dc";
+}
+
+.fa.fa-superpowers {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-wpexplorer {
+  font-family: 'Font Awesome 5 Brands';
+  font-weight: 400;
+}
+
+.fa.fa-cab:before {
+  content: "\f1ba";
+}
+/*# sourceMappingURL=v4-shims.css.map */

Dosya farkı çok büyük olduğundan ihmal edildi
+ 2 - 0
vendor/fontawesome-free/scss/v4-shims.css.map


Bu fark içinde çok fazla dosya değişikliği olduğu için bazı dosyalar gösterilmiyor