index

廣告(第一回合)

· 6min
ewd · 廣告(第一回合)

回到條碼的命題,將其視為一種「遊戲規則」,而創作是試著給出可行的答案,這是遊戲的第一回合。

將詩集文字轉換為 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 內建文檔。

Ref