廣告(第一回合)
· 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 內建文檔。