cheukblog

又是美好的一日
告示

cheukblog

不知道為什麼搞一個 Blog 出來,總之好像好好玩?

日誌
發布於 2026年10月04日

雜談 #1

看了 Blog 有一年了吧?而我最喜歡的文章之一一定是廢文小天地的雜談系列,不像其他的文章會好像很高深,很複雜,就是一個人分享最近的事情和想法,就很日常。而我也來寫寫吧!反正這是我的部落格,我想寫什麼大便也可,自爽一下。

這次寫寫這一兩個月的事情吧,之後範圍應該會縮小很多。

日記

新年的其中一個目標是寫日記,一開始的一兩個月是幾乎是每天都有寫,但是之後就開始懶散起來,幾天至一週才有一篇,暑假的時候從宿舍回家住的時間甚至完全沒有在寫。現在回宿舍,雖然也是差不多要變成週記了,但至少有寫已經是很好了啦~

成功養成習慣

九月從家中度假完回到宿舍,現在每天定時六七點學音樂,八點學日文,非常充實!( 雖然大部分時間是在耍廢就對了 )

Beatoraja

最近開始玩新音遊: Beatoraja, 一個 BMS 的模擬器,聽到了很多高質的音遊音樂,有一些真的很好聽啊,也有很多 00 年代,很有年代滄桑的老音樂,包括經典的 FREEDOM DIVE。

看到荼毒室的兩成員

前陣子搭地鐵的時候正巧看見荼毒室的四哥,還坐我隔壁,超巧的( 超興奮的 ),另外之前在又一城看到(危)1,還穿着之前常穿的淺粉色衣服,也是超級無敵巧的。。我看到他不知道為什麼有一種滄桑的感覺?當然,兩次我也沒有打招呼。


  1. 知道的知道 O_O ↩︎

日誌
發布於 2026年10月04日

部落格問題挑戰

無聊在不同的部落格上跳來跳去,看到了這篇:《部落格問題挑戰》,不過其實我應該在很久之前在 wiwi.blog 上已經讀過啦,也打算寫寫看,但一直都沒有。今天剛好又看到,就寫寫吧!算一算,現在我也寫了部落格差不多一年了吧?( 雖然沒什麼更新就是了 )

- 你當初為什麼開始寫部落格?
- 你使用什麼平台來管理你的部落格?為什麼選擇它?
- 你之前有在其他平台上寫過部落格文章嗎?
- 你如何撰寫文章?例如,使用本地編輯工具,還是在部落格的後台/控制面板中編寫?
- 你什麼時候最有寫作靈感?
- 你會在寫完後立即發布,還是會先存成草稿醞釀一下?
- 你部落格上最喜歡的文章是哪一篇?
- 你對部落格有什麼未來計畫嗎?例如重新設計、搬到另一個平台,或是加入新功能?

你當初為什麼開始寫部落格?

其實當初是看到 Wiwi 的部落格推廣,加上我好像也一直沒有什麼社交網站,覺得寫寫東西感覺幾過癮的,也可以記錄一下自己的想法,所以就用 bearblog 設了這個網站,之後又移到這裏。

你使用什麼平台來管理你的部落格?為什麼選擇它?

用 Hugo,就很方便啊!我也不想弄一大堆複雜的電腦東西( 雖然是電機系的 ),所以就選擇了 Hugo,還有設主題也很方便。然後 back-end 是 Github 加 Cloudfare,也是很基本的,沒有什麼特別的。

你之前有在其他平台上寫過部落格文章嗎?

有!前面提到我用了 bearblog 設了我第一個網站,不過之後又轉移在這裏,原因除了不想溫習拖延時間去搞 Hugo 和 Github,也是因為我很宅啦,想研究看看部落格這一個對我而言很新穎的東西,畢竟我是 Gen Z 嘛,我是真的因為 wiwi 而了解到這一個那麼有趣的圈子的。

你如何撰寫文章?例如,使用本地編輯工具,還是在部落格的後台/控制面板中編寫?

又是那麼技術性的問題⋯⋯ 好吧,現在我是用 Obsidian 去 push 到 Github 上的。折騰了很久,不過現在很滿意。

你什麼時候最有寫作靈感?

「寫作靈感」聽起來好有作家的感覺喔!但其實我現在都是在星期六日去圖書館寫寫(現在也是),沒什麼寫作靈感可言啦,主要就是分享想法和生活,記錄一下當我的筆記這樣子,也沒有打算作文什麼的。

你會在寫完後立即發布,還是會先存成草稿醞釀一下?

立即發布,一氣呵成寫完整篇,不然到時候又拖延症發作了⋯⋯

你部落格上最喜歡的文章是哪一篇?

哇,這個問題我真的沒什麼想⋯⋯ ,應該是這篇吧。

你對部落格有什麼未來計畫嗎?例如重新設計、搬到另一個平台,或是加入新功能?

說不定啦,哪天我不想做正經事的時候,可能會搞一搞設計。

日誌
發布於 2026年09月24日

Minecraft Splash

抄襲 Minecraft 的 Splash,更新了標題下的 subtitle,現在會隨機顯示:

[params]
  subtitles = [
    "又是美好的一日",
    "⑨ ⑨ ⑨ ⑨ ⑨",
    "你好中国",
    "師兄呀⋯⋯",
    "吓?",
    "仲未諗到打咩",
    "F5 多一次",
    "感動",
    "我們沒有敵人",
    "地",
    "真係好撚煩",
    "jonuigyugihljkuioihoijui",
    "應該是 cheuk's blog 但不知怎樣改",
  ]

對,很白痴但我喜,然後終於有機會偷渡 code block 了 > _ <

日誌
發布於 2026年09月20日

筷子家教

讀到 Wiwi 這篇《筷子的自由》,讓我想起之前看直播的時候也有提到類似的內容。總之就是裡面提到有人拿筷子的方式不正確,然後被人指責,那是長輩看到就一定會糾正的姿勢。而我當然也同意 Wiwi 的觀點:這是我的自由咩,又沒有傷害到人。但其實這個話題還有更深層的討論,就是某種程度上這其實反映了個人的家教與小時候受到的教育。例如當一個人很有禮貌、講究禮儀(像是會正確拿筷子、懂說話的方式,或是會主動幫忙諸如此類),我們就會覺得他有「家教」;相反地,如果一個人很邋遢又不懂得說話,就會讓人覺得不舒服,甚至想:「這人沒家教吧?」而有趣的是,這種情況就好像去國外一樣,我們也會去學習一些有的沒的禮儀。(像是在韓國,他們用餐時不能把碗端起來,因為那樣看起來像乞丐。)

然後,我覺得其實有一些這類看似沒什麼道理的禮儀,還是有它存在的必要性吧。因為當要在某些場合展現尊重時,不知該說是悲慘還是怎樣,往往就只有這些無謂的規範——不然你要怎麼表達自己很有禮貌、尊重長輩?(雖然我覺得尊重是因為你做了什麼,而不是因為對方的身份。)而禮貌又是中華文化非常重視的元素,這就導致了這些荒謬規範的發生。

對我來說,禮儀這種東西我是不會那麼介意啦,因為我家並沒有很嚴格的規定,禮貌方面也覺得無所謂。

註: 提到的節目(廣東話)

日誌
發布於 2026年09月11日

晚上不睡覺

這是「BlogBlog 同樂會 - 2026 年 9 月」的投稿文章。本月主題是「晚上不睡覺」,由 Jason Lai 主持。如果你有自己的部落格,歡迎一起來參加!

寫這個的時候剛好是暑假完結後,雖然現在要上早課,被逼要早起,但是在暑假期間(大概六到八月),拖到一兩點晚上不睡覺是根本的,有時候到三點半也有可能。而我晚上不睡覺通常都會躺在床上滑手機,看一些有的沒的,古靈精怪的東西,而社交媒體倒沒什麼在看耶,可能是因為我已經沒用很久了吧。

古靈精怪的東西

古靈精怪的東西根本上就是到處按 hyperlink,連到什麼就看什麼。像是關於 Blog 的,BBP、nownownow 或其他的,通常就是看到什麼有趣的站名就點下去,有點像社交媒體上的 doom-scroll,但我就不想碰到演算法和用社交媒體咩,而偶爾也會找到一些寶藏啦,想想,現在也有可能有人不知道為什麼點到我這裡。

除了這個之外,回想起來,我也會和 Gemini 聊一下天,而聊天不是很廢的那一種,而是很認真的那一種辯論,當然不在很哲學的那種,就認真去聊聊某一個話題。

不過,大部分的時間都是看看直播,看看劇,都是一些沒什麼營養的東西。

早起了啦

暑假完了,上大二了,回到宿舍,把身體時鐘拉回到十一點睡(有人也是住宿舍反而生活變健康嗎?),也很少打遊戲。其實,早起像是一種健康人生的開始吧?就好像什麼你平時都早起,然後都會默認你其他像作息和時間管理也很好。而現實上好像也是耶,Wiwi 提到晚上不會被打擾到,但對我來說,早上反而是不那麼會被打擾的,可能是我回到宿舍了吧,對於大學生,早上沒課的話當然會睡飽飽啦,而我就是看準這個時機,早上出去大學沒什麼人的時間,甚至星期日我也習慣去圖書館溫習,沒什麼人的環境總是讓我感覺很舒服~ 就沒有什麼人迫人的壓迫感,感覺心情不會被那麼「打擾」到,早上也不會有其他人傳訊息給我。

我想這也是我回家會晚睡吧,根本沒有早上不會被打擾的因素。反而真的像 Wiwi 說的,在半夜大家都睡了。

日誌
發布於 2026年08月24日

把 YouTube 擋掉

NewPipe 要沒落了,可是手機的網頁版 YouTube 又很爛,所以用了一段時間用用研究擋掉所有分心的東西。

  1. Untrap for Youtube
  2. SponsorBlock
  3. uBlock 在 Custom Filters 寫這兩個
m.youtube.com##ytm-fullscreen-related-videos-entry-point-view-model
m.youtube.com##.ytFullscreenVideoRecommendationsHost

把這個去掉 -> 圖片
然後就可以時間浪費其他不是在演算法上的影片了。

日誌
發布於 2026年08月16日

大便文

請找出「太」便。

大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便大便

日誌
發布於 2026年08月16日

一期一會

這是我的「BlogBlog 同樂會 - 2026 年 8 月」的投稿文章。本月主題是「一期一會」,由 ikuka 主持。如果你有自己的部落格,歡迎一起來參加!

參加了前兩期 BBP,然後之後就因為考試和懶惰所以沒有再更新⋯⋯ 現在有興致就快快寫吧!不然開學等下又不想寫⋯⋯

一期一會,一生只有一次的相遇。看到這主題其實想到很多一些我自己的真實例子耶,什麼中學畢業禮啊、畢業聚餐之類的,但可能我就沒什麼跟我同學聊天的,所以沒什麼留戀的⋯⋯

反而,最讓我有「一期一會」的感覺是小時候網上打遊戲的人。

小時候打 TF2(不知道是什麼,表示你是 GEN Z 了)的時候,常常就會去一些什麼掛機服,然後遇到一些很??的玩家,例如在 VC 上 CHING 來 CHING 出、大叫或說 xxx ,總之什麼屁孩都有啦,然後,當時也會去 Jailbreak 的服玩玩聊天或做一些很白痴的行為(主要)。然而,這些服都已經消失或已經沒有線上玩家了⋯⋯

GOOD OLD TIME :)

真懷念那一段時光,一放學就會進那些伺服器,雖然不知道真實的樣子或甚至是男是女,但 ID 都識得出來,會說 Hello。還記得當時不知道為什麼伺服器的人很歧視台灣人,就一看到就踢的那一種,想起就覺得自己很白痴 www。還有都會嫌棄屁孩在吵什麼的⋯⋯

作為玩了萬四小時(對)的老屁股就只能緬懷當時啦,雖然現在一些外國的伺服器還是有啦,甚至到新年倒數就會開的也有,但已經回不來當時天真的時光了⋯⋯

日誌
發布於 2026年08月11日

中文文案排版指北

在 Youtube 或其他網站上常看到不好的中文排版,所以如果你看到,請五分鐘看完這篇,個人打字潔淨度直接上升 87%,考試每次都拿一百分。

日誌
發布於 2026年08月11日

YT Music sidechain

Youtube 看教學但又不想音樂直接蓋過人聲,所以用 Gemini 寫了個音樂 sidechain 的 script。

現在學 C++ 可以有背景音樂,爽。

Script -> 點我(失效)

更:

// ==UserScript==
// @name         Universal Audio Sidechain with UI (v6.3 Multi-Tab Stabilized)
// @namespace    http://tampermonkey.net/
// @version      6.3
// @description  Hijacks native audio pipelines with sidechain ducking. Multi-tab registry prevents volume pumping.
// @author       You
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
// @run-at       document-start
// ==/UserScript==

(function () {
  'use strict';

  // Helper to safely parse localStorage values
  function getStoredFloat(key, defaultValue) {
      const val = localStorage.getItem(key);
      if (val === null) return defaultValue;
      const parsed = parseFloat(val);
      return isNaN(parsed) ? defaultValue : parsed;
  }

  // Generate unique ID for this browser tab instance
  const TAB_ID = 'tab_' + Math.random().toString(36).substring(2, 9);

  // =========================================================================
  // USER CONFIGURATION (DEFAULT SETTINGS)
  // =========================================================================
  const CONFIG = {
    ENABLED: true,
    DUCK_VOLUME: getStoredFloat('sc_duck_volume', 0.10),
    ATTACK_TIME: getStoredFloat('sc_attack_time', 0.05),
    RELEASE_TIME: getStoredFloat('sc_release_time', 0.60),

    CARRIER_DOMAINS: [
        'www.youtube.com',
        'twitch.tv',
        'bilibili.com'
    ],
    MODULATED_DOMAINS: [
        'music.youtube.com',
        'app.chillhop.com',
        'open.spotify.com',
        'soundcloud.com',
        'music.apple.com'
    ]
  };
  // =========================================================================

  const currentHost = window.location.hostname;
  const isCarrier = CONFIG.CARRIER_DOMAINS.some(domain => currentHost.includes(domain));
  const isModulated = CONFIG.MODULATED_DOMAINS.some(domain => currentHost.includes(domain));

  if (!isCarrier && !isModulated) return;

  // ==========================================
  // 1. CARRIER BEHAVIOR (Sender - Tab Registry System)
  // ==========================================
  if (isCarrier) {
    window.addEventListener('DOMContentLoaded', () => {
      let lastTriggerState = false;
      let lastWriteTime = 0;
      let triggerHoldTimer = 0;

      // Clean up tab registration on exit
      window.addEventListener('beforeunload', () => {
        const carriers = GM_getValue('sc_active_carriers') || {};
        delete carriers[TAB_ID];
        GM_setValue('sc_active_carriers', carriers);
      });

      setInterval(() => {
        const mediaElems = document.querySelectorAll('video, audio');
        let isPlaying = false;

        for (const media of mediaElems) {
          // Filter out tiny thumbnail preview elements (e.g. YouTube hover videos)
          const isMiniPreview = media.offsetWidth > 0 && media.offsetWidth < 180 && media.offsetHeight < 180;
          if (!isMiniPreview && !media.paused && !media.ended && media.readyState > 2) {
            isPlaying = true;
            break;
          }
        }

        const now = Date.now();
        if (isPlaying) {
          triggerHoldTimer = now;
        }

        // 800ms hold time prevents volume pumping during video buffering or seek transitions
        const isTriggered = isPlaying || (now - triggerHoldTimer < 800);

        if (isTriggered !== lastTriggerState || now - lastWriteTime > 400) {
          const carriers = GM_getValue('sc_active_carriers') || {};

          if (isTriggered) {
            carriers[TAB_ID] = now;
          } else {
            delete carriers[TAB_ID];
          }

          // Prune stale tabs (>3000ms inactive)
          for (const id in carriers) {
            if (now - carriers[id] > 3000) {
              delete carriers[id];
            }
          }

          GM_setValue('sc_active_carriers', carriers);
          lastTriggerState = isTriggered;
          lastWriteTime = now;
        }
      }, 50);
    });
  }

  // ==========================================
  // 2. MODULATED BEHAVIOR (Target Receiver + UI)
  // ==========================================
  if (isModulated) {

    const injectCode = `
      (function(DUCK_VOL, ATTACK, RELEASE, INITIAL_ENABLE) {
        let duckTarget = 1.0;
        let duckCurrent = 1.0;
        let configDuckVol = DUCK_VOL;
        let configAttack = ATTACK;
        let configRelease = RELEASE;
        let isEnabled = INITIAL_ENABLE;

        const htmlMedia = new Set();
        const webAudioGains = new Set();
        let activeAnalyser = null;
        const historyScope = new Array(200).fill(1.0);

        // [Engine 1]: HTMLMediaElement Hijack
        const origVolDesc = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'volume');
        if (origVolDesc) {
            Object.defineProperty(HTMLMediaElement.prototype, 'volume', {
                get() {
                    return this._userBaseVol !== undefined ? this._userBaseVol : origVolDesc.get.call(this);
                },
                set(v) {
                    this._userBaseVol = v;
                    origVolDesc.set.call(this, v * (isEnabled ? duckCurrent : 1.0));
                }
            });
            const origPlay = HTMLMediaElement.prototype.play;
            HTMLMediaElement.prototype.play = function() {
                htmlMedia.add(this);
                if (this._userBaseVol === undefined) this._userBaseVol = origVolDesc.get.call(this);
                origVolDesc.set.call(this, this._userBaseVol * (isEnabled ? duckCurrent : 1.0));
                return origPlay.apply(this, arguments);
            };
        }

        // [Engine 2]: Web Audio API Hijack
        const OriginalAudioContext = window.AudioContext || window.webkitAudioContext;
        if (OriginalAudioContext) {
            window.AudioContext = new Proxy(OriginalAudioContext, {
                construct(target, args) {
                    const ctx = new target(...args);
                    const masterGain = ctx.createGain();
                    const analyser = ctx.createAnalyser();
                    analyser.fftSize = 512;
                    activeAnalyser = analyser;

                    masterGain.gain.value = isEnabled ? duckCurrent : 1.0;
                    masterGain.connect(analyser);
                    analyser.connect(ctx.destination);
                    webAudioGains.add(masterGain);

                    Object.defineProperty(ctx, 'destination', { get: () => masterGain });
                    return ctx;
                }
            });
            window.webkitAudioContext = window.AudioContext;
        }

        // [DSP & Animation Loop]
        let lastTime = performance.now();
        function loop() {
            const time = performance.now();
            const dt = Math.min((time - lastTime) / 1000, 0.1);
            lastTime = time;

            const activeTarget = isEnabled ? duckTarget : 1.0;

            // Math precision snapping prevents floating-point volume jitter
            if (Math.abs(duckCurrent - activeTarget) > 0.001) {
                const step = activeTarget < duckCurrent
                    ? (1 / Math.max(0.01, configAttack)) * dt
                    : (1 / Math.max(0.01, configRelease)) * dt;

                if (activeTarget < duckCurrent) {
                    duckCurrent = Math.max(activeTarget, duckCurrent - step);
                } else {
                    duckCurrent = Math.min(activeTarget, duckCurrent + step);
                }

                applyGainToAll(duckCurrent);
            } else if (duckCurrent !== activeTarget) {
                duckCurrent = activeTarget;
                applyGainToAll(duckCurrent);
            }

            historyScope.push(duckCurrent);
            if (historyScope.length > 200) historyScope.shift();

            renderVisualizers();
        }

        function applyGainToAll(gainVal) {
            htmlMedia.forEach(m => {
                if(m._userBaseVol !== undefined) {
                    origVolDesc.set.call(m, m._userBaseVol * gainVal);
                }
            });

            webAudioGains.forEach(g => {
                if(g.context.state !== 'closed') g.gain.value = gainVal;
            });
        }

        function renderVisualizers() {
            const cAdsr = document.getElementById('sc-canvas-adsr');
            if (cAdsr) {
                const ctx = cAdsr.getContext('2d');
                const w = cAdsr.width;
                const h = cAdsr.height;
                ctx.clearRect(0, 0, w, h);

                ctx.strokeStyle = '#222';
                ctx.lineWidth = 1;
                ctx.beginPath();
                ctx.moveTo(0, h * 0.5); ctx.lineTo(w, h * 0.5);
                ctx.stroke();

                const pad = 8;
                const totalDur = configAttack + 0.3 + configRelease;
                const xAttack = pad + (configAttack / totalDur) * (w - pad * 2);
                const xSustain = xAttack + (0.3 / totalDur) * (w - pad * 2);
                const xRelease = w - pad;

                const yMax = pad;
                const yDuck = pad + (1 - configDuckVol) * (h - pad * 2);

                ctx.strokeStyle = '#00d2ff';
                ctx.lineWidth = 2;
                ctx.beginPath();
                ctx.moveTo(pad, yMax);
                ctx.lineTo(xAttack, yDuck);
                ctx.lineTo(xSustain, yDuck);
                ctx.lineTo(xRelease, yMax);
                ctx.stroke();

                let dotX = pad;
                let dotY = pad + (1 - duckCurrent) * (h - pad * 2);

                if (duckCurrent < 0.99) {
                    if (duckTarget < 1.0) {
                        dotX = pad + (1 - (duckCurrent - configDuckVol) / Math.max(0.001, 1 - configDuckVol)) * (xAttack - pad);
                    } else {
                        dotX = xSustain + ((duckCurrent - configDuckVol) / Math.max(0.001, 1 - configDuckVol)) * (xRelease - xSustain);
                    }
                }

                ctx.fillStyle = '#ff0055';
                ctx.beginPath();
                ctx.arc(Math.min(w - pad, Math.max(pad, dotX)), dotY, 4, 0, Math.PI * 2);
                ctx.fill();
            }

            const cScope = document.getElementById('sc-canvas-scope');
            if (cScope) {
                const ctx = cScope.getContext('2d');
                const w = cScope.width;
                const h = cScope.height;
                ctx.fillStyle = '#0a0a0a';
                ctx.fillRect(0, 0, w, h);

                ctx.lineWidth = 1.5;
                ctx.strokeStyle = '#00ff66';
                ctx.shadowColor = '#00ff66';
                ctx.shadowBlur = 4;
                ctx.beginPath();

                if (activeAnalyser && activeAnalyser.context.state === 'running') {
                    const bufferLength = activeAnalyser.frequencyBinCount;
                    const dataArray = new Uint8Array(bufferLength);
                    activeAnalyser.getByteTimeDomainData(dataArray);

                    const sliceWidth = w / bufferLength;
                    let x = 0;
                    for (let i = 0; i < bufferLength; i++) {
                        const v = dataArray[i] / 128.0;
                        const y = (v * h) / 2;
                        if (i === 0) ctx.moveTo(x, y);
                        else ctx.lineTo(x, y);
                        x += sliceWidth;
                    }
                } else {
                    const sliceWidth = w / historyScope.length;
                    let x = 0;
                    for (let i = 0; i < historyScope.length; i++) {
                        const y = h - (historyScope[i] * (h - 10) + 5);
                        if (i === 0) ctx.moveTo(x, y);
                        else ctx.lineTo(x, y);
                        x += sliceWidth;
                    }
                }
                ctx.stroke();
                ctx.shadowBlur = 0;
            }
        }

        setInterval(loop, 16);

        window.addEventListener('message', e => {
            if (e.source !== window || !e.data) return;
            if (e.data.type === 'SC_DUCK') duckTarget = configDuckVol;
            if (e.data.type === 'SC_RESTORE') duckTarget = 1.0;
            if (e.data.type === 'SC_CONFIG_UPDATE') {
                configDuckVol = e.data.duckVol;
                configAttack = e.data.attack;
                configRelease = e.data.release;
                isEnabled = e.data.enabled;
                if (duckTarget < 1.0) duckTarget = configDuckVol;
            }
        });
      })(${CONFIG.DUCK_VOLUME}, ${CONFIG.ATTACK_TIME}, ${CONFIG.RELEASE_TIME}, ${CONFIG.ENABLED});
    `;

    const script = document.createElement('script');
    script.textContent = injectCode;
    document.documentElement.appendChild(script);
    script.remove();

    // UI Control Panel
    function renderControlUI() {
      if (document.getElementById('sc-ui-panel')) return;

      const panel = document.createElement('div');
      panel.id = 'sc-ui-panel';
      panel.style.cssText = `
        position: fixed; bottom: 20px; left: 20px; z-index: 999999;
        background: rgba(18, 18, 18, 0.94); color: #fff;
        padding: 12px 16px; border-radius: 10px;
        border: 1px solid rgba(255, 255, 255, 0.15);
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        font-size: 12px; width: 240px; box-shadow: 0 8px 24px rgba(0,0,0,0.5);
        backdrop-filter: blur(8px); pointer-events: auto; user-select: none;
      `;

      panel.innerHTML = `
        <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:10px; border-bottom:1px solid rgba(255,255,255,0.1); padding-bottom:6px;">
          <span style="font-weight:700; color:#00d2ff; display:flex; align-items:center; gap:5px;">🎛️ Sidechain DSP</span>
          <span id="sc-toggle-btn" style="cursor:pointer; color:#888; font-size:11px;">[Collapse]</span>
        </div>
        <div id="sc-body">
          <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
            <label style="color:#ccc;">Enable Sidechain</label>
            <input type="checkbox" id="sc-chk-enable" ${CONFIG.ENABLED ? 'checked' : ''} style="cursor:pointer;">
          </div>

          <div style="margin-bottom:8px;">
            <div style="display:flex; justify-content:space-between; color:#bbb; margin-bottom:3px;">
              <span>Duck Volume:</span>
              <span id="sc-val-duck">${Math.round(CONFIG.DUCK_VOLUME * 100)}%</span>
            </div>
            <input type="range" id="sc-rng-duck" min="0" max="1" step="0.01" value="${CONFIG.DUCK_VOLUME}" style="width:100%; cursor:pointer; accent-color:#00d2ff;">
          </div>

          <div style="margin-bottom:8px;">
            <div style="display:flex; justify-content:space-between; color:#bbb; margin-bottom:3px;">
              <span>Attack:</span>
              <span id="sc-val-attack">${Math.round(CONFIG.ATTACK_TIME * 1000)}ms</span>
            </div>
            <input type="range" id="sc-rng-attack" min="0.01" max="1" step="0.01" value="${CONFIG.ATTACK_TIME}" style="width:100%; cursor:pointer; accent-color:#00d2ff;">
          </div>

          <div style="margin-bottom:8px;">
            <div style="display:flex; justify-content:space-between; color:#bbb; margin-bottom:3px;">
              <span>Release:</span>
              <span id="sc-val-release">${Math.round(CONFIG.RELEASE_TIME * 1000)}ms</span>
            </div>
            <input type="range" id="sc-rng-release" min="0.05" max="2" step="0.05" value="${CONFIG.RELEASE_TIME}" style="width:100%; cursor:pointer; accent-color:#00d2ff;">
          </div>

          <div style="margin-top:10px; margin-bottom:6px;">
            <div style="color:#aaa; font-size:10px; margin-bottom:2px;">Envelope ADSR Curve:</div>
            <canvas id="sc-canvas-adsr" width="240" height="45" style="background:#111; border-radius:4px; border:1px solid #333; display:block;"></canvas>
          </div>

          <div style="margin-top:6px; margin-bottom:6px;">
            <div style="color:#aaa; font-size:10px; margin-bottom:2px;">Oscilloscope:</div>
            <canvas id="sc-canvas-scope" width="240" height="45" style="background:#0a0a0a; border-radius:4px; border:1px solid #333; display:block;"></canvas>
          </div>

          <div id="sc-status" style="font-size:10px; text-align:right; color:#777; margin-top:6px; font-weight:600;">
            Status: Idle
          </div>
        </div>
      `;

      document.body.appendChild(panel);

      const chkEnable = document.getElementById('sc-chk-enable');
      const rngDuck = document.getElementById('sc-rng-duck');
      const rngAttack = document.getElementById('sc-rng-attack');
      const rngRelease = document.getElementById('sc-rng-release');
      const toggleBtn = document.getElementById('sc-toggle-btn');
      const scBody = document.getElementById('sc-body');

      let isCollapsed = false;
      toggleBtn.addEventListener('click', () => {
        isCollapsed = !isCollapsed;
        scBody.style.display = isCollapsed ? 'none' : 'block';
        toggleBtn.innerText = isCollapsed ? '[Expand]' : '[Collapse]';
      });

      const updateConfig = () => {
        CONFIG.ENABLED = chkEnable.checked;
        CONFIG.DUCK_VOLUME = parseFloat(rngDuck.value);
        CONFIG.ATTACK_TIME = parseFloat(rngAttack.value);
        CONFIG.RELEASE_TIME = parseFloat(rngRelease.value);

        localStorage.setItem('sc_duck_volume', CONFIG.DUCK_VOLUME);
        localStorage.setItem('sc_attack_time', CONFIG.ATTACK_TIME);
        localStorage.setItem('sc_release_time', CONFIG.RELEASE_TIME);

        document.getElementById('sc-val-duck').innerText = Math.round(CONFIG.DUCK_VOLUME * 100) + '%';
        document.getElementById('sc-val-attack').innerText = Math.round(CONFIG.ATTACK_TIME * 1000) + 'ms';
        document.getElementById('sc-val-release').innerText = Math.round(CONFIG.RELEASE_TIME * 1000) + 'ms';

        window.postMessage({
          type: 'SC_CONFIG_UPDATE',
          enabled: CONFIG.ENABLED,
          duckVol: CONFIG.DUCK_VOLUME,
          attack: CONFIG.ATTACK_TIME,
          release: CONFIG.RELEASE_TIME
        }, '*');
      };

      [chkEnable, rngDuck, rngAttack, rngRelease].forEach(el => el.addEventListener('input', updateConfig));
    }

    window.addEventListener('DOMContentLoaded', () => {
      renderControlUI();
      setInterval(() => {
        if (!document.getElementById('sc-ui-panel')) renderControlUI();
      }, 2000);
    });

    let localTriggerState = false;

    function checkActiveCarriers(carriers) {
      if (!carriers || typeof carriers !== 'object') return false;
      const now = Date.now();
      for (const id in carriers) {
        if (now - carriers[id] < 3000) {
          return true; // Active playing carrier tab exists
        }
      }
      return false;
    }

    function updateStatusUI(isTriggered) {
      const statusEl = document.getElementById('sc-status');
      if (statusEl) {
        if (!CONFIG.ENABLED) {
            statusEl.innerText = 'Status: Disabled';
            statusEl.style.color = '#777';
        } else if (isTriggered) {
            statusEl.innerText = 'Status: 🔊 Ducking Audio';
            statusEl.style.color = '#00d2ff';
        } else {
            statusEl.innerText = 'Status: 🎵 Normal Output';
            statusEl.style.color = '#888';
        }
      }
    }

    GM_addValueChangeListener('sc_active_carriers', (name, oldValue, newValue) => {
      const isTriggered = checkActiveCarriers(newValue);

      if (isTriggered !== localTriggerState) {
          window.postMessage({ type: isTriggered ? 'SC_DUCK' : 'SC_RESTORE' }, '*');
          localTriggerState = isTriggered;
          updateStatusUI(isTriggered);
      }
    });

    // Fallback registry heartbeat scan
    setInterval(() => {
      const carriers = GM_getValue('sc_active_carriers');
      const isTriggered = checkActiveCarriers(carriers);

      if (isTriggered !== localTriggerState) {
        window.postMessage({ type: isTriggered ? 'SC_DUCK' : 'SC_RESTORE' }, '*');
        localTriggerState = isTriggered;
        updateStatusUI(isTriggered);
      }
    }, 500);
  }
})();

日誌
發布於 2026年08月11日

Yeah

用了幾小時,終於用 Cloudflare 和 Obsidian 架了我的 Blog 了!作為一個高中沒讀過電腦的我表示驕傲。

但是,雖然搞了那麼久,感覺之後又會因為懶然後不寫 Blog…

日誌
發布於 2026年08月02日

你好世界

用了 Gemini Flash 搞了一個沒有 JavaScript 很 light-weighted 的 Touhou 主題出來,最下面還說什麼 Pure CSS 和什麼奧義,笑死。

然後,什麼更新的就到時候才說吧。

日誌
發布於 2026年02月27日

只有我這樣嗎?

以前的文

這是我的「BlogBlog 同樂會 - 2026 年 2 月」的投稿文章。本月主題是「只有我這樣嗎?」,由 Wiwi 主持。如果你有自己的部落格,歡迎一起來參加!

「只有我這樣嗎?」這一個問題,我覺得是每一個寫 Blog 的人都偶爾會有的感受吧,畢竟會寫部落格這種我朋友都不知道的東西應該都是社會上的異類。而說起異類,我有時真的覺得我一些個性和行為是挺奇怪的。

結巴

從小到大我就是個很內向的人,不會主動找人聊天或什麼的,可能也是這樣吧,慢慢產生結巴的問題,而結巴問題真的好煩啊!而每次不敢或者不想與別人對話多數都是因為怕結巴,怕講話不完整,當然也其他原因啦,結巴只是其中一個。不過,上到大學之後,我發現結巴通常都是一想到說什麼就會發生,然後我自己言論審查自己,結果就結巴了,現在講話會少一點想東想西的,而雖然問題還是在,但至少改善了許多。

早上猫

上大學之前沒有學業壓力,差不多今天都是三四點才睡覺,然後十二點起床,現在想想我也是很糜爛呢。不過,上了大學宿舍後,情況是一百八十度轉變了,變成早上猫,而早上猫是真的很早上猫,早上洗澡、溫習,每天都一定要食早餐。

聽歌不聽歌手

可能是我樂理看太多的關係,聽歌的時候,其他人都可能會第一時間留意到歌手,但我好像絕大部份的情況都不會,總是留意別的元素,像是最基本的和弦進行、編曲、Bass Line 到可能真的很音樂 nerd 才會留意的,像是什麼合成音色、sidechain、外音之類的,其實想想也挺搞笑的,其他人心裏想這首歌真動感,而我就會想「這個 glitch percussion 怎麼弄的啊?」。

喜歡總是留意「配角」

最後我覺得「只有我這樣嗎?」(有些假掰的意義)的時刻就是我總是留意平常不被人留意的地方,一些背景的「配角」。像是上面我說的不聽歌手,除了因為樂理宅的關係,最大的影響就是我不想背景的人物像是結他手、鍵盤手的努力被忽略、被不看見,當然,歌手在絕大部分的音樂基本上 80% 的成功都是靠他啦,但不代表他們不重要。而再進一步,我覺得主流似乎太過注重於歌手這一個部分,某程度上變成一種「歌手主義」?前面的鋼琴部份跳過,中間的 Bridge 跳過,只播 Chrous 的部分,不過好像跳遠了,這個另寫文章吧。總之,我就是喜歡留意「配角」,這不限於音樂,像是運動,明星球員的買點是努力,但每一個球員其實都很努力啊⋯⋯、成功人士,背後有多少的「失敗者」?

以上就是我生活上的「只有我這樣嗎?」。

日誌
發布於 2026年01月29日

推抗

以前的文

這是我的 2026 年 1 月份 BlogBlog 同樂會投稿

說到推抗,說真的我沒有什麼想推抗,也沒有推抗過朋友什麼東西。音樂上,一直在聽很 HARDCORE 的音遊音樂,但應該會喜歡聽就是只有玩音遊的人罷了,而知識方面,像是數學的話,呃⋯⋯,不要好了,好難寫。

但是,說到生活上令人的 quality of life 上升的東西,我想到了幾個。

電動牙刷\

一次逛超市的時候,無意中看到便宜的就買到了,用到現在,很滿意,改善了一直懶惰刷牙的習慣 :p。

水壺\

喝水很重要,不過一直以來都沒有水壺,而最近買了個水壺,挺滿意的,買個9s自己裝水,節省了一些錢,也能順便戒喝飲料,雖然偶爾也會喝喝,但其實改良了不少。

MacroDroid\

它可以實現手機上許多的 AUTOMATION,像我就設了晚上充電提示、 自動 MUTE 手機,UI 像 SCRATCH,不會太難。

散步\

觀察一下身邊的事物,走一段沒有走過的路,或是放下手機,用感官探索這個世界,看一看以住一直經過的風景,會有意想不到的收穫。

推了生活上的東西,但感覺還是要抗一下其他的啊!

Altale\

一款叫 DEEMO 的音遊裏的一首曲子,結合了電子和原聲樂器,很動人,很 EXPRESSIVE。

lost found melody\

音遊曲子,電音,不知道要怎麼形容,總之就很酷。

總之就是音遊音樂啦。

啊,最後!

【哲學係咁傾】\

香港的哲學清談節目,內容很有趣,有中文字幕,不會廣東話的也可以看。

以上就是我的推抗文,什麼都推抗一下,細節就自己看看吧,我也不可能形容電動牙刷是如何震我的口吧(?)