廣告(第一回合)
· 6min
回到條碼的命題,將其視為一種「遊戲規則」,而創作是試著給出可行的答案,這是遊戲的第一回合。
將詩集文字轉換為 unicode ,並以 code 39 條碼的方式呈現,聲音則是將二進制檔案轉成音訊資料,並設定序列播放,產生一種純粹資訊化的噪聲。
使用 #supercollider 與 #cablesgl ,如果對宅宅的技術原理有興趣可見下方原始碼:
Code
英數字 -> code39 ,有參考的部份應該都有列在下方。
(因為很酷所以加入了「檢查碼」和「檢查碼的所在位置」的輸出)
// 2025@ewd
// Ref:
// https://gist.githubusercontent.com/incubated-geek-cc/985c079f4ad9be869e4febcaa63e9893/raw/f60096838d9bb74e1c9c5c676de2c0b059607baf/code39Mapping.js
const charMapper = {
"0":["2B", "2W", "2B", "5W", "5B", "2W", "5B", "2W", "2B", "2W"],
"1":["5B", "2W", "2B", "5W", "2B", "2W", "2B", "2W", "5B", "2W"],
"2":["2B", "2W", "5B", "5W", "2B", "2W", "2B", "2W", "5B", "2W"],
"3":["5B", "2W", "5B", "5W", "2B", "2W", "2B", "2W", "2B", "2W"],
"4":["2B", "2W", "2B", "5W", "5B", "2W", "2B", "2W", "5B", "2W"],
"5":["5B", "2W", "2B", "5W", "5B", "2W", "2B", "2W", "2B", "2W"],
"6":["2B", "2W", "5B", "5W", "5B", "2W", "2B", "2W", "2B", "2W"],
"7":["2B", "2W", "2B", "5W", "2B", "2W", "5B", "2W", "5B", "2W"],
"8":["5B", "2W", "2B", "5W", "2B", "2W", "5B", "2W", "2B", "2W"],
"9":["2B", "2W", "5B", "5W", "2B", "2W", "5B", "2W", "2B", "2W"],
"*":["2B", "5W", "2B", "2W", "5B", "2W", "5B", "2W", "2B", "2W"],
"A":["5B", "2W", "2B", "2W", "2B", "5W", "2B", "2W", "5B", "2W"],
"B":["2B", "2W", "5B", "2W", "2B", "5W", "2B", "2W", "5B", "2W"],
"C":["5B", "2W", "5B", "2W", "2B", "5W", "2B", "2W", "2B", "2W"],
"D":["2B", "2W", "2B", "2W", "5B", "5W", "2B", "2W", "5B", "2W"],
"E":["5B", "2W", "2B", "2W", "5B", "5W", "2B", "2W", "2B", "2W"],
"F":["2B", "2W", "5B", "2W", "5B", "5W", "2B", "2W", "2B", "2W"],
"G":["2B", "2W", "2B", "2W", "2B", "5W", "5B", "2W", "5B", "2W"],
"H":["5B", "2W", "2B", "2W", "2B", "5W", "5B", "2W", "2B", "2W"],
"I":["2B", "2W", "5B", "2W", "2B", "5W", "5B", "2W", "2B", "2W"],
"J":["2B", "2W", "2B", "2W", "5B", "5W", "5B", "2W", "2B", "2W"],
"K":["5B", "2W", "2B", "2W", "2B", "2W", "2B", "5W", "5B", "2W"],
"L":["2B", "2W", "5B", "2W", "2B", "2W", "2B", "5W", "5B", "2W"],
"M":["5B", "2W", "5B", "2W", "2B", "2W", "2B", "5W", "2B", "2W"],
"N":["2B", "2W", "2B", "2W", "5B", "2W", "2B", "5W", "5B", "2W"],
"O":["5B", "2W", "2B", "2W", "5B", "2W", "2B", "5W", "2B", "2W"],
"P":["2B", "2W", "5B", "2W", "5B", "2W", "2B", "5W", "2B", "2W"],
"Q":["2B", "2W", "2B", "2W", "2B", "2W", "5B", "5W", "5B", "2W"],
"R":["5B", "2W", "2B", "2W", "2B", "2W", "5B", "5W", "2B", "2W"],
"S":["2B", "2W", "5B", "2W", "2B", "2W", "5B", "5W", "2B", "2W"],
"T":["2B", "2W", "2B", "2W", "5B", "2W", "5B", "5W", "2B", "2W"],
"U":["5B", "5W", "2B", "2W", "2B", "2W", "2B", "2W", "5B", "2W"],
"V":["2B", "5W", "5B", "2W", "2B", "2W", "2B", "2W", "5B", "2W"],
"W":["5B", "5W", "5B", "2W", "2B", "2W", "2B", "2W", "2B", "2W"],
"X":["2B", "5W", "2B", "2W", "5B", "2W", "2B", "2W", "5B", "2W"],
"Y":["5B", "5W", "2B", "2W", "5B", "2W", "2B", "2W", "2B", "2W"],
"Z":["2B", "5W", "5B", "2W", "5B", "2W", "2B", "2W", "2B", "2W"],
"-":["2B", "5W", "2B", "2W", "2B", "2W", "5B", "2W", "5B", "2W"],
",":["5B", "5W", "2B", "2W", "2B", "2W", "5B", "2W", "2B", "2W"],
"$":["2B", "5W", "2B", "5W", "2B", "5W", "2B", "2W", "2B", "2W"],
"/":["2B", "5W", "2B", "5W", "2B", "2W", "2B", "5W", "2B", "2W"],
"+":["2B", "5W", "2B", "2W", "2B", "5W", "2B", "5W", "2B", "2W"],
"%":["2B", "2W", "2B", "5W", "2B", "5W", "2B", "5W", "2B", "2W"],
"_":["2B", "5W", "5B", "2W", "2B", "2W", "5B", "2W", "2B", "2W"]
};
const strIn = op.inString("String", "test");
const arrOut = op.outArray("Array");
const uniOut = op.outString("Unicode");
const checkDigitOut = op.outString("Check Digit");
const checkDigitIdxOut = op.outNumber("checkDigitIdxOut");
const toReplaceUni = {
"2B": "▌",
"5B": "█▌",
"2W": "",
"5W": " "
};
const toReplaceArr = {
"2B": 0,
"5B": 1,
"2W": 0,
"5W": 1
};
let charMapperObj = {};
let binMapperObj = {};
for(let char in charMapper) {
let arr = charMapper[char];
let barcode = "";
let binStr = [];
for(let bw of arr) {
barcode += toReplaceUni[bw];
binStr += toReplaceArr[bw];
}
charMapperObj[char] = barcode.trim();
binMapperObj[char] = binStr;
}
let check = [];
for(let char in charMapper) {
check.push(char);
}
let uniBarcode = "";
let binBarcode = [];
let checkDigitIdx = 0;
let strInArr = [];
strIn.onChange = update;
function update() {
strInArr = strIn.get() ? strIn.get().trim().toUpperCase().split("") : [];
for(let strInChar of strInArr) {
if (!"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890*-,$/+_%".includes(strInChar)) {
strIn.set("");
break;
}
uniBarcode += charMapperObj[strInChar];
let bin = binMapperObj[strInChar];
let binArr = bin
.split("")
.map(item => parseInt(item, 10));
binBarcode.push(binArr);
}
// check digit
let indexSum = 0;
let checkDigit = 0;
strInArr.map((item) => {
indexSum += check.indexOf(item);
});
checkDigit = indexSum ? check[indexSum % check.length] : "";
checkDigitIdx = checkDigit ? check.indexOf(checkDigit) : null;
uniOut.set(uniBarcode);
arrOut.set(binBarcode);
checkDigitOut.set(checkDigit);
checkDigitIdxOut.set(checkDigitIdx);
}supercollider databending,原碼有點混亂有點懶得整理,不過大致原理是:
readAllSignal 讀取 -> 丟進 buffer -> 丟進 Dictionary
~path = PathName(thisProcess.nowExecutingPath).parentPath++"assets/";
~makeBuffers = {
d = Dictionary.new;
PathName(~path).entries.do{
arg subfolder, index;
d.add(
subfolder.folderName.asSymbol ->
subfolder.entries.collect {
arg item, index;
Buffer.loadCollection(s, File.readAllSignal(item.fullPath).clip2(1));
}
);
};
};播放的時候用 SynthDef,在 bufnum 的位置放入 buffer 在 Dictionary 裡的 index 就可以了……
SynthDef.new(\data, {
arg out, bufnum, amp = 0.3, rate = 1, spos = 0, spd = 2.0, pan = 0;
var sig, env, gate;
gate = Pulse.kr(spd);
sig = PlayBuf.ar(1, bufnum, rate, startPos: spos * BufFrames.kr(bufnum), doneAction: 2);
env = EnvGen.kr(Env([0, 1, 0], [0.01, 0.5], \hold), gate, doneAction: 2);
sig = sig * env * amp;
sig = Pan2.ar(sig, pan);
Out.ar(out, sig);
}).add;
// ......
~blend0 = Pbind(
\instrument, \data,
\bufnum, d[\blend][0],
\dur, 2,
\spd, Pseq([0.5, 0.25, 2.0, 1.75, Rest(0.5), 20.0], inf),
\spos, 0.4,
\amp, 0.3,
\rate, Pfunc { [1, 2, 0.8, 4, 0.5].choose },
\pan, Pseq([0.25, 0.5, -0.5, -0.3, 0.25], inf),
\group, ~mainGrp,
\out, ~out
).play;
看不懂?沒關係,我也不是很懂,但至少跟在 audacity 測試的結果差不多。
這東西真〇○複雜,難怪需要在 IDE 內建文檔。