Modular Computers the manual GitHub

Audio Sensor

An audio sensor hears what a player standing at its block would hear, and reports each sound: which one, and where it came from.

Facts

| | | |—|—| | Type | hearing | | Address | An expansion slot path such as exp:0, or a hub’s | | Range | The game’s own range for each sound | | Interrupt | Raised while events wait | | Power | 9 W | | Info | kind (audio_sensor) |

It reports the sound the game plays, by its id, such as minecraft:entity.pig.ambient, and nothing else about it (no category, no mob). It is exactly what the game plays: a skeleton’s step is minecraft:entity.skeleton.step, and the stone under it is silent. It hears every sound the server sends to players, and the sounds players make of world events (a block broken plays its own break sound). A sound counts if a player at the sensor’s block would hear it. It works while a running computer powers it.

In mcos

/dev/hearing0 (hearing1 for the next) is a read-only file, an event a line:

minecraft:entity.pig.ambient 1.5 0.0 -3.0

That is sound x y z. A read with nothing waiting waits for the next event, and poll reports the file readable (IN) while one waits. When events were dropped, a line lost n comes before the next one. Opening it to write fails with EACCES.

tail -f /dev/hearing0

The file’s ioctls are the sensor’s methods, by name, in any open mode: sys.ioctl(fd, "pull") answers the oldest event as a table, with numbers for its coordinates. A sensor that is gone answers ENODEV. setLabel labels it, as for any device (see The sys library).

The file and pull take from the same buffer, in order: an event read from the file is gone from pull, and the other way round. Polling loses nothing: an event a poll found waiting is the next one pull answers (or the next line read), so a program can wait with sys.poll and then take events with pull until it answers nil.

Methods

pull() → event or nil

The oldest sound heard and waiting, as an event table (see Events), or nil when none waits. Each is taken once.

local event = sys.ioctl(fd, "pull")  -- {sound = "minecraft:entity.pig.ambient", x = 1.5, y = 0.0, z = -3.0}

Events

Field Value
sound the sound’s id
x, y, z where it came from, relative to the block’s center, in blocks, to one decimal place
lost present when events were dropped: how many

Each sound heard raises the sensor’s interrupt, which stays raised while any wait. At most 32 wait. When more come, the oldest goes and the next event carries lost. Events are kept across a save, and dropped while no program runs. The directions are the world’s. A sound card starting to play is heard once at its start.

Examples

Run these with lua name.lua &, from a computer with an audio sensor. For why they loop without yielding, see Writing programs.

Count the first five sounds

local f = io.open("/dev/hearing0")
for i = 1, 5 do
  print(i, f:read("l"))  -- waits for each sound
end

Log a base’s doors and chests

-- Log the sounds of a base's doors, chests and broken blocks: each sound the audio sensor hears
-- whose id matches one of WATCH is appended to a log with the world's time and where it came from
-- ("Day 2 14:03:10 minecraft:block.chest.open 2.5 0.0 -1.5").
--
-- It reads the sensor's file, a line a sound: read blocks until one is heard. The log is opened and
-- closed for each line, so it is on the disk at once (a disk's file shows its writes at close).
local LOG = "/home/sounds.log"
local WATCH = {               -- Lua patterns over a sound's id
  "door%.",                   -- any door or trapdoor opening or closing
  "^minecraft:block%.chest%.",
  "^minecraft:block%.barrel%.",
  "%.break$",                 -- a block broken
}

local function watched(sound)
  for _, pattern in ipairs(WATCH) do
    if sound:find(pattern) then
      return true
    end
  end
  return false
end

local sensor = assert(io.open("/dev/hearing0"))
for line in sensor:lines() do
  local sound = line:match("^(%S+) %S+ %S+ %S+$")
  if sound and watched(sound) then
    local log = assert(io.open(LOG, "a"))
    log:write(os.date("%c"), " ", line, "\n")
    log:close()
  elseif line:match("^lost ") then
    local log = assert(io.open(LOG, "a"))
    log:write(os.date("%c"), " (", line, " sounds)\n")
    log:close()
  end
end

Download audio-log.lua

The patterns are Lua’s: %. is a dot. tail -f /home/sounds.log follows the log.

Point toward the last sound

-- Point at the last sound: twice a second, the redstone card lights the one face that looks most
-- toward the last sound the audio sensor heard (east, west, up, down, south or north), and darkens
-- the others. Put lamps on the block's faces and they show where the noise is.
--
-- It uses the sensor's method, pull, through its file's ioctls (each sound a table, its x, y and z
-- numbers: x grows east, y up, z south), and the redstone card's set, every face in one call.
local sys = require("sys")

local LOOK = 0.5 -- seconds between looks

local sensor = assert(sys.open("/dev/hearing0"))
local card = assert(sys.open("/dev/redstone0"))

-- The face toward a direction: the axis it is most along.
local function toward(x, y, z)
  local ax, ay, az = math.abs(x), math.abs(y), math.abs(z)
  if ax >= ay and ax >= az then
    return x >= 0 and "east" or "west"
  elseif ay >= az then
    return y >= 0 and "up" or "down"
  end
  return z >= 0 and "south" or "north"
end

local lit
while true do
  local last
  local sound = sys.ioctl(sensor, "pull")
  while sound do -- (the newest one heard since the last look)
    last = sound
    sound = sys.ioctl(sensor, "pull")
  end
  if last then
    local face = toward(last.x, last.y, last.z)
    if face ~= lit then
      local faces = {}
      for _, name in ipairs({"north", "south", "east", "west", "up", "down"}) do
        faces[name] = {powered = name == face and 15 or 0}
      end
      sys.ioctl(card, "set", faces)
      lit = face
    end
  end
  sys.sleep(LOOK)
end

Download audio-pointer.lua

It needs a redstone card in the same computer. The directions are the world’s, so the faces it lights are too, however the computer is turned.