3 min read

回顧《禾日鑲聲》

Table of Contents

訊號流

這整個演出的設備工作 flow 大概是:

Strudel → OSC → SC → Pipewire → Audio Interface

演出計劃

主要是因為活動偏向展覽性質,演出加上解說時間總加大約在十分鐘以內,因此採取一邊操作並解說的模式進行,畢竟 live coding 實在也沒什麼演出的規定之類的,感覺也很難介定哪邊是真正的開始或結束,只要我想達到的最終狀態有呈現出來,覺得就算是達到目標。

技術與困難

應該是從線性編曲轉向 live coding 創作有些不太習慣,當然對它還不算很熟也是個原因,抓不清楚可以達到什麼程度的表現,所幸官方的 doc 還蠻平易近人……

後來摸出一套流程可供參考:

  1. 先決定樂曲基本要素(調性、BPM、風格……)
  2. 在 DAW 試做一個「主題」(數小節的循環) 和預計加入的聲響
  3. 按照需求將 sample 切片 (1/4、1/8、1/16……),方便對上時間軸,也比較不會產生 pitch 改變的問題

hydra 的整合

strudel 裡可以直接把 hydra 也放進去一起使用,甚至可以直接在裡面做聲音觸發、連動 BPM 等,省去不少工具整合的麻煩。

Code

//   __    __  _______   __    __   ______
//  /  |  /  |/       \ /  |  /  | /      \
//  $$ |  $$ |$$$$$$$  |$$ |  $$ |/$$$$$$  |
//  $$ |__$$ |$$ |__$$ |$$  \/$$/ $$ \__$$/
//  $$    $$ |$$    $$<  $$  $$<  $$      \
//  $$$$$$$$ |$$$$$$$  |  $$$$  \  $$$$$$  |
//  $$ |  $$ |$$ |  $$ | $$ /$$  |/  \__$$ |
//  $$ |  $$ |$$ |  $$ |$$ |  $$ |$$    $$/
//  $$/   $$/ $$/   $$/ $$/   $$/  $$$$$$/
//
// HRXS Project @ 2025 by ewd

await initHydra();

setcpm(120 / 2);

let kP = '<k  [~ [~ [~ k]]] k ~ k [~ [~ [~ k]]] [k [~ k] k ~] ~> / 2';
let sP = '<~ s ~ s> / 2';
let pP = '<0 2 3 4> 1 <2 0 13 2> 4 <13 0 1 2> 2';

let bt = 64;

// Macro
const pMacro = register('pMacro', (degrade, jux, bin, iter, pat) =>
    pat
        .degradeBy(degrade)
        .juxBy(jux, (x) => x.hurry('0.5 3 8 2.1 2.3 7 4').iter(iter))
        .struct(binaryN('1000 2000 1999', bin))
        .ply('<2 3 4 2> / 4')
        .slow('<4 3 8 6>')
        .coarse('7 | 5 | 10'),
);

let colors = arrange([bt, s('<colors> / 64')], [bt, s('<colors:1> / 64')]);

let felt = arrange([bt / 2, s('felt / 32')], [bt / 2, s('felt:1 / 32')]);

// -------START HERE--------- //

//sine
// GAIN MUTE ONLY
sine: s('<~ sine ~ sine> / 4')
    .n('0 | 1 | 2 | 3')
    // .chop("16")
    .striate('16')
    .degrade()
    .legato(1)
    .often((x) => x.jux(hurry(4)))
    .room(0.3)
    .gain(0);

// eco
_eco: s('<eco>')
    .n(choose('0 | 1 | 2 | 3 | 4'))
    .loopAt(8)
    .room(0.1)
    // .delay(0.4).dt(0.3)
    .hpf(300)
    .gain(0.8);

// swt
_swt: s('<swt> / 4')
    .n('<1> | <2> | <3> | <4>')
    // .jux(x => x.chop(4))
    .hpf(600)
    .gain(0.8);

// p
// pMacro(degrade[0 - 1], jux[0 - 1], bin[1 - 32], iter[1 - 4])
// "mute" not working

_p: s('p')
    .n(pP)
    .pMacro(0.4, 0.3, 16, 2)
    .phaser('<0.5 0.2 0.3 0.5> / 4')
    .phaserdepth('0.3')
    .gain(1);

// chime
_chime: s('chime')
    .n('0 | 1 | 2 | 3')
    .jux((x) => x.chop(4))
    .degradeBy('0.7')
    .legato(0.5)
    .room(0.1)
    .gain(0.7);

// xylo
_xylo: s('xylo')
    .striate(choose('32 | 15 | 8 | 4'))
    .degrade()
    .sometimesBy(0.8, (x) => x.hurry('2').coarse(2))
    .slow('<4 5 6>')
    .shape(0.3)
    .gain(1);

// piano colors
_colors: colors.gain(1.3);

// drum
_drum: stack(s(kP), s(sP)).shape(0.2).room(0.4).delay(0.2).gain(1);

// felt piano
_felt: felt.gain(1.5);

_chord: arrange(
    [
        bt / 2,
        stack(s('chord / 32'), s('chord:1 / 32').hpf(500).coarse(10).gain(0.3)),
    ],
    [
        bt / 2,
        stack(
            s('chord:2 / 32'),
            s('chord:1 / 32').hpf(500).coarse(10).gain(0.3),
        ),
    ],
).gain(1.3);

// Hydra

osc(60, 0.05, 0.1)
    .diff(
        osc(H('<10 5 1 5> / 4'), H('<0.1 0.2 0.1 0.4> / 4'), 0.3).rotate(
            () => (time % 360) * 0.2,
        ),
    )
    .modulateScale(
        // <<-
        voronoi(20, 1)
            // shape(10, 0.8, 0.2)
            // osc(20, 0.3, 0.2)
            // noise(5, 0.2)
            .modulate(src(o0).rotate(time % 360)),
        H('<0.1 0.3 0.3 0.1> / 4'), // <--
    )
    .color(H('<1 0.9 0.8> / 8'), 0.5, 0.6, H('<0.2 0.1 0.3> / 8'))
    .blend(src(o0).modulatePixelate(o0), H('<0.2 0.3 0.5> / 16'))
    .luma(0.3, 0.5)
    .invert()
    // .kaleid(H("<4, 8, 12, 6> / 16"))
    .blend(src(o1), 0.8) //alpha
    .out(o0);