우리 집에는 지난 이사 때 아직 풀지 못한 짐 상자가 가득한 수납방이 있다. 이삼 주 전부터 이 방 어딘가에서 간헐적으로 삐 소리가 나기 시작했다. 소리가 나는 간격이 워낙 길어서, 방에 서서 출처를 가려내려 해도 번번이 허탕이었다. 그렇다고 그 많은 상자를 하나하나 뒤지고 싶지도 않았다. 그래서 Claude Code에게 Beep Locator를 만들게 했다. 방 곳곳에 놓은 여러 기기로 정체불명의 삐 소리 위치를 삼각측량하는 소프트웨어 시스템이다.
Beep Locator를 한동안 돌려 두면 이런 모습이 된다.

Python 웹 서버를 실행하면 메인 UI가 뜬다. 여기에 표시되는 QR 코드나 URL로 여유 있는 기기를 최대한 많이 접속시켜 리스너로 쓰면 된다. 나는 iPad 두 대, 휴대폰 한 대, MacBook Pro, Surface Ultra를 사용했다.
그러면 기기들이 서로에게 소리를 내보내며 자신의 위치를 추정한다. 이 방식은 그다지 잘 맞지 않았다. 그래서 "Rough mode"를 추가했다. 기기 아이콘을 공간 위에서 끌어다 대략적인 위치를 직접 지정하면, 이후 소리 정보로 그 위치를 보정해 주는 방식이다.
설정이 끝나면 모든 기기가 삐 소리를 기다린다!
박수 모드도 넣었다. 방 안 여러 곳에서 손뼉을 쳐서 테스트할 수 있고, 박수 위치는 지도에 다이아몬드로 표시된다. 덕분에 이 시스템이 소리의 위치를 실제로 찾아낼 수 있다는 확신을 얻었다.
삐 소리는 지도에 X 표시로 나타나고, "Sounds located" 로그에도 한 줄씩 기록된다. 기록된 소리는 기기별로 차례로 다시 들어볼 수 있다. 찾던 정체불명의 삐 소리가 맞다면 "This is the mystery beep"를 눌러 표시해 두면 된다.
정체불명의 삐 소리를 확정하고 나면, 시스템은 그 소리만 골라 듣고 삐 소리 사이의 간격을 측정한다. 삼각측량으로 얻은 위치들을 종합해 지도에서 가장 유력한 지점도 좁혀 간다.
시스템은 의도대로 작동했다. 삐 소리의 위치를 40cm 안팎의 오차로 짚어 주었고, 그 상자 더미를 뒤졌더니 오래된 일산화탄소 감지기가 나왔다. 배터리를 갈아 달라고 애타게 울고 있었던 것이다.
Claude와 이 문제를 풀어 가기 시작하자마자, Claude는 2007년 논문 BeepBeep: A High-Accuracy Acoustic-Based System for Ranging and Localization Using COTS Devices를 찾아냈다. 저자는 Chunyi Peng, Guobin Shen, Yongguang Zhang, Yanlin Li, Kun Tan이다. 논문 초록은 다음과 같다.
우리는 음향 기반의 고정밀 거리 측정 시스템 BeepBeep의 설계, 구현, 평가를 소개한다. 이 시스템은 사전에 구축된 인프라 없이, 즉흥적인 애드혹 방식으로 기기 간에 동작한다. 순수 소프트웨어 방식이며, 스피커, 마이크, 기기 간 통신 수단이라는 가장 기본적인 범용 하드웨어만 있으면 된다. 따라서 저비용 센서 플랫폼은 물론 휴대폰, PDA 같은 대부분의 상용 기성품(COTS) 모바일 기기에도 곧바로 적용할 수 있다. BeepBeep은 양방향 센싱, 자체 녹음, 샘플 카운팅이라는 세 가지 기법을 결합해 높은 정확도를 달성한다.
우리가 만들려던 바로 그 시스템처럼 보였다!
Beep Locator는 대부분의 작업을 Python 웹 애플리케이션 서버가 처리한다. 리스너 기기는 WebSocket으로 이 서버에 접속해 UI를 띄우고, 브라우저의 마이크 접근 권한을 요청한다. 리스너의 주된 역할은 최근 60초 분량의 오디오를 버퍼에 계속 녹음해 두는 것이다. 또한 간단한 삐 소리 감지 알고리즘(detector.js 참고)도 함께 돌린다. 이 알고리즘은 고속 푸리에 변환(FFT)과 하이패스 필터로 삐 소리 후보를 가려내고, 감지 이벤트를 메인 서버로 보낸다.
삐 소리가 보고되면 서버는 기기 하나(서버와의 왕복 시간이 가장 짧은 기기)에 짧은 처프(chirp) 소리를 내게 한다. 그런 다음 모든 리스너에게 삐 소리 약 0.6초 전부터 처프 이후 0.8초까지의 클립을 업로드하라고 요청한다. 이 처프 덕분에 클립들을 서로 정렬할 수 있으며, 이는 삐 소리 위치를 찾는 데 핵심적인 단계다.
이 프로젝트는 iPhone에서 Claude Code Cloud로 시작했고, 모델은 Opus 5.5를 Extra thinking effort 모드로 사용했다. 새로 만든 빈 GitHub 저장소에 처음 입력한 프롬프트와 그 세션은 공유된 시작 세션에서 볼 수 있다.
Write out a problem.md file describing the problem we want to solve
There is a device beeping in a room and I cannot find it
I want to set up several devices - laptops, phones, iPads - around the room all listening at the same time and have them help triangulate where the beep comes from
There needs to be a Python web app which coordinates it all - one device shows a web page for the app, other devices join a URL which turns them into listeners - all via browser WebAudio and WebSockets back to the Python server app
Ideally I wouldn't need to manually input the relative locations of the devices - it would be amazing if they could play sounds to each other in order to locate each other in 2D space - is that even possible though?
그 결과 나온 것이 problem.md 파일이다. 이 문서를 작성하는 과정에서 Claude가 BeepBeep 논문을 찾아 읽었고, 그 내용도 파일에 정리되어 있다.
조사 내용이 괜찮아 보여서 이렇게 지시했다.
Ok try and build it, add a mechanism so you can send new code to the listeners which they will then run automatically - that way I can set everything up in the room and then leave you alone to continue hacking and experiment while I do something else
그렇게 이 시점까지 왔다. 시스템 대부분이 만들어졌지만, 아직 실제로 실행해 본 적은 없었다.
새 저장소를 노트북에 클론하고 Claude Code를 실행했는데, 마침 Fable 5.2가 출시되었다. 이 프로젝트가 Fable 5.2의 성능을 시험해 볼 좋은 기회로 보였다.
노트북의 Claude Code로 서버를 띄운 뒤, 무슨 일이 벌어지는지 볼 수 있게 하는 것은 물론 그리고 소프트웨어가 실행되는 도중에도 실시간으로 수정할 수 있게 해 두었다.
이 기능은 이미 소프트웨어에 들어 있었다. Opus 5.5가 리스너에 installModule() 메서드를 만들어 두었기 때문이다. WebSocket 연결로 이 메서드를 호출할 수 있어서, 관리자 서버가 클라이언트 어디에서든 새 코드를 임의로 실행시킬 수 있다.
일부 기기에서는 마이크를 사용하는 웹 페이지가 새로고침될 때마다 사용자 권한을 다시 요청한다. 핫스왑 모듈 방식 덕분에 리스너 기기를 일일이 손으로 조작하지 않고도 새 코드를 배포할 수 있었다.
그 뒤로 내가 한 일은 대체로 노트북 옆에 앉아 Fable 5.2와 상황을 이야기하고, 가끔 새 기능을 요청하는 것이었다. 실제로 입력한 기능 프롬프트 몇 개를 소개한다.
I think we need to make it so I can drag things around on the map in “rough mode”, where my job is just to put them roughly where they are, then switch out of rough mode and it attempts to get them accurate
덕분에 리스너를 지도 위에서 끌어 옮길 수 있게 되었다.
what’s a good way for me to test this I wonder? Can we have it listen for claps, then I could clap and it could identify the location of the clap? [...]
Claps should use a different symbol on the map than beeps
이 요청으로 박수 소리가 날 때마다 삐 소리와 다른 기호로 지도에 표시되는 기능이 생겼다.
each symbol on the map should include a HH:MM:SS timestamp
덕분에 어떤 기호가 실제로 어떤 소리인지 한눈에 구분하기가 쉬워졌다.
The log should show beeps and claps with their timestamps AND have a little play button which, when clicked, replays the recorded noise from each of the listeners in sequence (with clear text on screen showing which listener recorded which snippet) while temporarily turning off microphone recording while that replay plays
이제 소리를 다시 재생해 볼 수 있어서, 녹음된 클립 중 어느 것이 찾던 정체불명의 삐 소리인지 훨씬 쉽게 가려낼 수 있었다.
There should be a button next to a recording for “This is the mystery beep” - then I can hit replay to see and then confirm it was the mystery beep if that’s the one I am looking for
Then the system can categorize noises in the future to see if they are likely that same mysterious beep and use that information to build a more accurate idea of where it is
이쯤 되니 거의 다 온 셈이었다. 삐 소리가 어디서 나는지 꽤 확실히 감이 잡혔다. 그런데 그 지점 주변으로 리스너를 옮겨 배치하면 위치를 더 정밀하게 좁힐 수 있지 않을까?
I want a feature for this, I don’t want to have to restart each listening device etc, I want to be able to click a “Reposition the devices” button which lets me switch into repositioning, then I click “Repositioned” and it puts me into Rough mode and asks me to roughly locate them, then I do that and click, then it starts up again. The previous session can be read-only viewed through the app.
마침 남는 기기가 하나 있어서 이렇게도 요청했다.
add a feature where the map includes a spot that is a suggestion for a location that it would be useful to add a listener at - based on data collected so far
이 프로젝트에서 가장 흥미로운 점은 Claude Code에 귀를 달아 줄 수 있음을 보여 줬다는 것이라고 생각한다.
Claude는 (아직) 오디오를 직접 입력받지 못한다. 하지만 마이크로 녹음한 오디오를 분석하는 JavaScript와 Python 코드는 실행할 수 있다.
나는 여러 리스너 기기와 WebSocket으로 연결되는 웹 서버를 실행하라고 지시했다. 그리고 각 기기에서 WebAudio 마이크를 돌리게 하는 동시에, 그 WebSocket으로 새 JavaScript 코드를 보내 실행시킬 수 있도록 연결하게 했다.
그다음부터는 Claude를 거의 내버려 둬도 됐다. Claude는 알아서 문제에 매달려 오디오 로그를 조회하고, 문제를 푸는 데 도움이 되도록 자신의 귀를 새로 설정해 가며 작업했다.
몇 주 전 GitHub Universe에서 들은 비슷한 해킹 사례도 떠오른다. Martin Woodward는 GitHub Universe Badge 컴퓨터를 USB-C로 컴퓨터에 연결하고 웹캠으로 화면을 비춰 가며 디버깅했다고 한다. 코딩 에이전트가 업데이트가 제대로 적용됐는지 스스로 확인할 수 있게 하려는 것이었다.
코딩 에이전트가 주변 세계를 인지하게 만드는 방법을 궁리하는 일은 정말 재미있다.
지금 보시는 것은 제 블로그의 장문 아티클뿐입니다. 모든 글을 받아 보려면 /atom/everything/을 구독하시거나, 다른 구독 방법을 확인해 보세요.