Create: Computing AgesBrass Docs
Devices

Jacquard Card Reader

The punch card sequencer: where it is in its deck, the four redstone strengths it gives, and the holes of every card it holds.

The Jacquard Card Reader is the head of Jacquard's loom of 1804, which read a chain of punched cards: the first programmable machine. A shaft turns it from below, and at each quarter turn it reads the next row of its cards and gives four redstone strengths, one on each side. On its own it runs a factory sequence without any computer. A computer connected to it follows that sequence: which card and which row it reads, what each side gives, and the holes of every card it holds.

Brass
local reader = peripheral.find("card_reader")
local p = reader.position()
print("card " .. p.card .. " of " .. p.cards .. ", row " .. p.row)
Methods
reader.position()Where the reader is in its deck, counted from 1, as the goggles show it:
reader.outputs()The redstone strength each side gives now, from the row being read. All four are 0 while the reader is empty or held.
reader.card(n)The holes of card n, as a list of 12 numbers, one per row. nil when the reader holds fewer than n cards. A number with decimals stops the program with bad argument #1 to 'card' (number has no integer representation).

How it works

Cards. A Blank Punch Card becomes a Punch Card in the Card Punch: 12 rows of 16 holes. Right-click the reader with a Punch Card to add it at the end of the chain, up to 9 cards; right-click with an empty hand to take the last one back. The reader reads its cards from the first slot up to the first empty one.

Rows. Each quarter turn of the shaft moves to the next row. After row 12 comes row 1 of the next card, and after the last card, the first one again. The faster the shaft, the faster the sequence: at 16 RPM, a little more than one row a second; at 256 RPM, about 17. The direction of rotation does not matter.

Holding it. A redstone signal from the block above (a lever on top of the reader, or redstone dust on it) holds the reader: it goes back to card 1, row 1, gives 0 on all four sides, and stops reading until the signal goes.

The four sides. The front is the face that looked at you when you placed the reader. Right and left are the reader's own, as for a person facing you: its right side is on your left. Put a Redstone Lamp on each side to check.

The holes. In the Card Punch, each row has four groups of four holes, marked Front, Right, Back and Left. The holes of a group are worth 8, 4, 2 and 1: the strength given on that side is the sum of the punched holes, from 0 (no hole) to 15 (all four). Engineer's Goggles show the card, the row and the four strengths.

Reaching it from a computer. Put the reader against a face of the computer, or, from the Minicomputer on, against a Data Cable of its network, where it is named like card_reader@120,64,-35. It turns and it holds cards, so it is also a kinetic block (reader.speed()) and an inventory of 9 slots, one card each. See Peripherals.

When the reader is gone, every method stops the program with card reader removed; when its chunk is not loaded, with peripheral is not loaded.

Following the sequence

#

reader.position()

→ table

Where the reader is in its deck, counted from 1, as the goggles show it:

Returns
table
{card =, row =, cards =, reset =}
fieldcontent
cardthe card being read, from 1 to cards; 0 when the reader is empty
rowthe row being read, from 1 to 12
cardshow many cards are chained, from 0 to 9
resettrue while the redstone from above holds the reader on its first row
Brass
local reader = peripheral.find("card_reader")
local p = reader.position()
if p.cards == 0 then
  print("put punch cards in the reader")
elseif p.reset then
  print("held on the first row by the redstone on top")
else
  print("card " .. p.card .. "/" .. p.cards .. ", row " .. p.row .. "/12")
end

See also card_reader.outputs() card_reader.card()

#

reader.outputs()

→ table

The redstone strength each side gives now, from the row being read. All four are 0 while the reader is empty or held.

Returns
table
{front =, right =, back =, left =}, each a redstone strength from 0 to 15

The sides are the reader's own (see "The four sides" above). This program prints each new row as it comes, the four strengths in that order:

Brass
local reader = peripheral.find("card_reader")
local last = ""
while true do
  local o = reader.outputs()
  local now = o.front .. " " .. o.right .. " " .. o.back .. " " .. o.left
  if now ~= last then
    print(now)
    last = now
  end
  sleep(0.05)
end

When two rows that follow each other give the same strengths, only the first one is printed.

See also card_reader.position() card_reader.card()

The cards

#

reader.card(n)

→ table|nil

The holes of card n, as a list of 12 numbers, one per row. nil when the reader holds fewer than n cards. A number with decimals stops the program with bad argument #1 to 'card' (number has no integer representation).

Parameters
n number
the place of the card in the chain, from 1
Returns
table|nil
the 12 rows of the card, one number each; nil when there is no card n

How a row becomes a number. Each punched hole adds its value. In the Front group, the holes add 8, 4, 2 and 1, as the Card Punch marks them; in the Right group, 16 times as much; in the Back group, 256 times; in the Left group, 4096 times. So a row is

front + 16 × right + 256 × back + 4096 × left

from 0 (a blank row) to 65535 (all 16 holes). Seen as a binary number of 16 bits, bit c (c from 0 to 15) is set when its hole is punched: bits 0 to 3 are the front strength, 4 to 7 the right one, 8 to 11 the back one and 12 to 15 the left one.

Brass has no bit operators: // (division rounded down) and % (the remainder) take a row apart.

Brass
local function strengths(row)
  return {
    front = row % 16,
    right = (row // 16) % 16,
    back = (row // 256) % 16,
    left = (row // 4096) % 16,
  }
end

local s = strengths(16138)
print(s.front, s.right, s.back, s.left)

16138 is 10 + 256 × 15 + 4096 × 3: this prints 10, 0, 15 and 3. A single hole works the same way: hole c is punched when row // 2 ^ c % 2 is 1, so row // 2 ^ 4 % 2 == 1 tests the hole worth 1 of the Right group.

The whole deck, card after card, with the four strengths of each row:

Brass
local reader = peripheral.find("card_reader")
for n = 1, reader.position().cards do
  print("card " .. n)
  print(" row front right back left")
  local rows = reader.card(n)
  for r = 1, 12 do
    local row = rows[r]
    print(string.format("%4d %5d %5d %4d %4d", r, row % 16, (row // 16) % 16, (row // 256) % 16, (row // 4096) % 16))
  end
end

See also card_reader.position()

Example: a sequencer dashboard

A screen beside the loom: the step being read, and a bar for each side. The reader runs the factory by itself; the computer only shows what it does.

Brass
local reader = peripheral.find("card_reader")
local SIDES = {"front", "right", "back", "left"}
while true do
  local p = reader.position()
  local o = reader.outputs()
  term.clear()
  term.set_cursor(1, 1)
  print("JACQUARD READER")
  if p.cards == 0 then
    print("no cards")
  elseif p.reset then
    print("held on the first row")
  else
    print("card " .. p.card .. "/" .. p.cards .. ", row " .. p.row .. "/12")
  end
  for _, side in ipairs(SIDES) do
    print(string.format("%-6s %2d %s", side, o[side], string.rep("#", o[side])))
  end
  sleep(0.1)
end