
리스트 프로세싱 - pack, trigger, zl
pack/pak으로 리스트 만들기, trigger로 실행 순서 제어, zl 시리즈로 리스트 자르고 모으고 정렬하기. 로또 번호 생성기 실습.
이 에피소드에서 배우는 것
- pack과 pak의 차이: 리스트를 만드는 두 가지 방법
- trigger로 실행 순서를 명시적으로 제어하는 방법
- zl 시리즈로 리스트를 자르고, 모으고, 정렬하는 방법
- 실습: 로또 번호 생성기 만들기
사전 지식
왜 리스트 프로세싱이 필요한가
시퀀서를 만들려면 음표들의 배열이 필요하다. 화음을 표현하려면 여러 Pitch를 하나로 묶어야 한다. 패턴을 생성하려면 데이터를 자르고 정렬해야 한다. 이 모든 작업의 기반이 리스트 프로세싱이다.
Max에서 리스트(List)란 공백으로 구분된 여러 값의 나열이다. 예를 들어 60 64 67은 C Major 화음의 세 음을 담은 리스트이다. 이 에피소드에서는 리스트를 만들고, 제어하고, 가공하는 핵심 도구 세 가지를 배운다.
pack vs pak: 리스트 생성
pack
[pack]은 여러 개의 값을 하나의 리스트로 묶어 출력하는 오브젝트이다.
[pack 0 0 0]
이렇게 만들면 세 개의 Inlet이 생기고, 각 Inlet에 값을 넣을 수 있다. 인자의 개수가 Inlet의 개수를 결정하며, 인자의 타입(정수, 소수, 심볼)이 각 Inlet이 받는 데이터 타입을 결정한다.
pack의 핵심 규칙: 왼쪽 Inlet에 값이 들어올 때만 출력된다.
예를 들어 [pack 0 0]에서 오른쪽 Inlet에 100을 보내면, 100은 저장되지만 아무것도 출력되지 않는다. 그 후 왼쪽 Inlet에 60을 보내면 60 100이 출력된다.
이 동작 방식은 Max의 일반적인 규칙, 즉 “오른쪽에서 왼쪽으로 값을 먼저 설정한 뒤, 왼쪽 Inlet으로 실행을 트리거한다”와 일치한다.
예시: [pack 0 0 0]
오른쪽 Inlet에 67 전송 → 저장만 됨, 출력 없음
중앙 Inlet에 64 전송 → 저장만 됨, 출력 없음
왼쪽 Inlet에 60 전송 → [60 64 67] 출력!
pak
[pak]은 pack과 거의 동일하지만, 어떤 Inlet에 값이 들어와도 출력된다는 점이 다르다.
[pak 0 0 0]
pak에서는 오른쪽 Inlet에 67을 보내면 즉시 0 0 67이 출력된다. 중앙에 64를 보내면 0 64 67이 출력된다. 모든 Inlet이 “뜨거운(hot)” Inlet인 셈이다.
| 오브젝트 | 출력 트리거 | 용도 |
|---|---|---|
[pack] | 왼쪽 Inlet에 값이 들어올 때만 | 값을 모아두었다가 한번에 출력할 때 |
[pak] | 어떤 Inlet에든 값이 들어올 때 | 값이 바뀔 때마다 즉시 반영할 때 |
실제 사용에서는 pack이 훨씬 많이 쓰인다. 원하는 타이밍에 출력을 제어하기 더 쉽기 때문이다. pak은 실시간 GUI 피드백 등 모든 변화를 즉시 반영해야 할 때 유용하다.
trigger (t): 실행 순서 제어
Max에서 가장 중요한 규칙 중 하나는 오른쪽에서 왼쪽으로 실행된다는 것이다. 하나의 Outlet에서 여러 곳으로 패치코드가 나갈 때, 오른쪽 연결이 먼저 실행되고 왼쪽 연결이 나중에 실행된다. 하지만 시각적으로 어느 쪽이 “오른쪽”인지 모호할 때가 있고, 패치가 복잡해지면 실행 순서를 예측하기 어려워진다.
[trigger](줄여서 [t])는 이 문제를 해결한다. 입력을 받으면 오른쪽 Outlet부터 왼쪽 Outlet 순서로 데이터를 출력한다. 실행 순서가 시각적으로 명확해진다.
[t b i f]
trigger의 인자는 출력할 데이터 타입을 지정한다.
| 인자 | 의미 | 설명 |
|---|---|---|
i | integer | 입력을 정수로 변환하여 출력 |
f | float | 입력을 소수로 변환하여 출력 |
b | bang | 입력에 관계없이 bang 출력 |
s | symbol | 입력을 심볼로 변환하여 출력 |
l | list | 입력을 리스트 그대로 출력 |
trigger의 두 가지 핵심 기능을 정리하면 다음과 같다.
1) 실행 순서 보장
[t b b b]
세 개의 Outlet이 있다면, 가장 오른쪽(세 번째)이 먼저, 그 다음 중앙(두 번째), 마지막으로 왼쪽(첫 번째)이 실행된다. 이 순서는 절대로 바뀌지 않는다.
2) 데이터 타입 변환
[t f i b]
입력으로 3.14가 들어오면:
- 오른쪽 Outlet: bang 출력 (원래 값 무시, bang만 보냄)
- 중앙 Outlet:
3출력 (정수로 변환) - 왼쪽 Outlet:
3.14출력 (소수 그대로)
pack에 값을 보내기 전에 trigger를 사용하는 것은 매우 흔한 패턴이다. 오른쪽 Inlet부터 순서대로 값을 채운 뒤, 마지막에 왼쪽 Inlet을 트리거하여 올바른 순서로 출력을 보장할 수 있다.
zl 시리즈: 리스트 처리 도구
[zl]은 Max에서 리스트를 다루는 스위스 아미 나이프 같은 오브젝트이다. zl 뒤에 모드 이름을 붙여서 다양한 리스트 처리를 수행한다. 여기서는 가장 자주 사용되는 5가지 모드를 다룬다.
zl slice: 앞에서부터 자르기
리스트를 지정한 위치에서 둘로 나눈다.
[zl slice 3]
입력: 10 20 30 40 50
- 왼쪽 Outlet:
10 20 30(앞의 3개) - 오른쪽 Outlet:
40 50(나머지)
인자가 3이면 “앞에서 3개를 잘라낸다”는 의미이다. 리스트에서 원하는 길이만큼 앞부분을 추출할 때 사용한다.
zl ecils: 뒤에서부터 자르기
slice의 역순이다. 이름도 slice를 뒤집은 “ecils”이다.
[zl ecils 2]
입력: 10 20 30 40 50
- 왼쪽 Outlet:
10 20 30(앞부분) - 오른쪽 Outlet:
40 50(뒤의 2개)
인자가 2이면 “뒤에서 2개를 잘라낸다”는 의미이다. 리스트의 끝부분을 추출할 때 유용하다.
zl group: 모아서 출력
개별적으로 들어오는 값들을 지정한 개수만큼 모은 뒤, 리스트로 한꺼번에 출력한다.
[zl group 4]
값을 하나씩 보내면:
- 1 입력 → (아직 출력 없음)
- 2 입력 → (아직 출력 없음)
- 3 입력 → (아직 출력 없음)
- 4 입력 →
1 2 3 4출력!
4개가 모이면 리스트로 출력하고 내부 버퍼를 비운 뒤 다시 모으기 시작한다. 스트림 데이터를 일정 크기의 청크로 나눌 때 핵심적인 오브젝트이다.
zl nth: 특정 인덱스 값 꺼내기
리스트에서 n번째 위치의 값을 꺼낸다. 중요: 인덱스가 1부터 시작한다. 프로그래밍 언어의 0-based 인덱스와 다르므로 주의해야 한다.
[zl nth 3]
입력: 10 20 30 40 50
- 왼쪽 Outlet:
30(3번째 값) - 오른쪽 Outlet:
10 20 40 50(3번째를 뺀 나머지)
인덱스를 동적으로 바꿀 수도 있다. 오른쪽 Inlet에 숫자를 보내면 인덱스가 변경된다. 시퀀서에서 현재 스텝의 값을 읽어올 때 핵심적으로 사용된다.
zl sort: 리스트 정렬
리스트의 값을 오름차순으로 정렬한다.
[zl sort]
입력: 30 10 50 20 40
- 출력:
10 20 30 40 50
내림차순 정렬이 필요하면 정렬 후 리스트를 뒤집는 추가 처리가 필요하다.
실습: 로또 번호 생성기
지금까지 배운 도구들을 조합하여 간단한 로또 번호 생성기를 만들어보자.
로또 번호의 조건:
- 1~45 사이의 숫자 6개
- 중복 없음
- 오름차순 정렬
사용할 오브젝트:
[urn 45]: 1~45 범위에서 중복 없이 난수를 생성한다. urn은 “항아리(urn)“에서 공을 하나씩 꺼내는 것처럼, 한 번 나온 숫자는 다시 나오지 않는다.[zl group 6]: 6개의 숫자가 모일 때까지 기다렸다가 리스트로 출력한다.[zl sort]: 6개의 숫자를 오름차순으로 정렬한다.
패치 구조:
[button] (bang)
|
[t b b]
| |
| [urn 45] ← clear 메시지를 먼저 보내 초기화
| |
| (bang 6번 반복을 위해 [uzi 6] 사용)
| |
| [urn 45]
| |
| [zl group 6]
| |
| [zl sort]
| |
| [message box] (결과 표시)
실제로는 다음과 같이 구현한다.
[button]을 클릭하면 bang이 발생한다.[t b b]가 오른쪽부터 실행한다. 먼저 오른쪽 bang이[urn 45]에clear메시지를 보내 초기화한다.- 왼쪽 bang이
[uzi 6]에 전달되어 bang을 6번 연속 발생시킨다. - 각 bang이
[urn 45]에서 중복 없는 숫자를 하나씩 꺼낸다. [zl group 6]이 6개를 모아 리스트로 만든다.[zl sort]가 오름차순 정렬하여 최종 결과를 출력한다.
trigger의 실행 순서 보장이 없으면 urn이 초기화되기 전에 숫자를 꺼내는 사고가 발생할 수 있다. trigger는 이런 타이밍 문제를 방지하는 핵심 장치이다.
Season 1 EP08의
lottery.maxpat실습 파일이 바로 이 패턴이다. 그 패치를 다시 열어서 구조를 분석해보자.
핵심 오브젝트 정리
| 오브젝트 | 역할 | 비고 |
|---|---|---|
[pack] | 여러 값을 리스트로 묶기 | 왼쪽 Inlet에서만 출력 트리거 |
[pak] | 여러 값을 리스트로 묶기 | 모든 Inlet에서 출력 트리거 |
[trigger] / [t] | 실행 순서 제어 + 타입 변환 | 오른쪽 → 왼쪽 순서 |
[zl slice] | 리스트 앞에서부터 자르기 | - |
[zl ecils] | 리스트 뒤에서부터 자르기 | slice의 역순 |
[zl group] | 값을 모아 리스트로 출력 | 지정 개수가 모이면 출력 |
[zl nth] | 리스트에서 n번째 값 추출 | 인덱스 1부터 시작! |
[zl sort] | 리스트 오름차순 정렬 | - |
[urn] | 중복 없는 난수 생성 | clear로 초기화 |
[uzi] | 지정 횟수만큼 bang 연속 발생 | - |
직접 해보기
[pack 0 0 0]과[pak 0 0 0]을 나란히 만들고, 같은 순서로 값을 보내보자. 출력 시점이 어떻게 다른지 확인하자.[t b i f]에 정수 42를 보내보자. 세 Outlet에서 각각 무엇이 나오는지, 어떤 순서로 나오는지 관찰하자.- 로또 번호 생성기를 직접 구현해보자. 추가 도전: 보너스 번호 1개를 별도로 표시하려면 어떻게 수정해야 할까?
다음 에피소드 예고
다음 에피소드에서는 리스트 프로세싱의 실전 활용으로, 8-Step Sequencer를 만든다. metro, counter, zl을 결합하여 Tresillo 리듬 패턴을 프로그래밍하고, MIDI로 출력하는 과정을 처음부터 끝까지 구현한다.
자주 묻는 질문
[pack]과 [pak]의 차이는 정확히 무엇인가요?
둘 다 여러 값을 하나의 리스트로 묶지만 출력 트리거 조건이 다릅니다. [pack]은 가장 왼쪽(Hot) Inlet에 값이 들어올 때만 출력하고, 그 외 Inlet은 값을 저장만 합니다. [pak]은 어떤 Inlet에 값이 들어와도 즉시 새 리스트를 출력합니다(모든 Inlet이 Hot). 실제로는 출력 시점을 제어하기 쉬운 [pack]이 훨씬 자주 쓰이며, [pak]은 GUI 컨트롤들의 변화를 실시간으로 모두 반영해야 할 때 사용합니다.
[zl nth]의 인덱스가 0부터 시작하나요, 1부터 시작하나요?
1부터 시작합니다. 이것이 Max 리스트 처리의 가장 흔한 함정입니다. [zl nth 1]은 첫 번째 요소, [zl nth 2]는 두 번째 요소를 꺼냅니다. counter는 0부터 세지만 zl nth는 1부터 인덱싱하므로, counter의 출력을 zl nth로 보낼 때는 [+ 1]을 거쳐야 정확합니다. 다른 zl 모드(slice, ecils 등)도 "개수" 의미라 자연수 기반이지만, 프로그래밍 언어의 0-based 배열에 익숙하다면 zl nth만큼은 따로 기억해두세요.
[trigger]는 [pack]과 어떻게 결합해서 쓰나요?
여러 값을 정확한 순서로 [pack]에 넣고 싶을 때 [trigger] 앞에 두는 것이 표준 패턴입니다. 예를 들어 하나의 입력으로 pack 0 0 0의 세 Inlet을 채우려면 [t i i i]를 두고 오른쪽부터 왼쪽 순서로 pack의 Inlet 3, 2, 1에 연결합니다. 이렇게 하면 trigger가 "pack의 오른쪽(Cold) Inlet부터 채우고 마지막에 왼쪽(Hot) Inlet으로 트리거"라는 Max의 황금률을 자동으로 보장하므로 실행 순서 버그가 사라집니다.
리스트의 길이를 알고 싶을 때는 어떤 오브젝트를 쓰나요?
[zl len] (또는 [zl length])이 입력 리스트의 요소 개수를 정수로 출력합니다. 예: [zl len]에 '10 20 30'을 보내면 3이 나옵니다. 다른 유용한 zl 모드로는 [zl reg]는 리스트를 저장하고 bang으로 다시 꺼내기, [zl rev]는 역순 정렬, [zl rot]는 회전, [zl mth]는 특정 인덱스 값만 꺼내고 원본은 그대로 보존(nth는 꺼낸 값을 빼고 출력함) 등이 있습니다. zl 시리즈는 매우 광범위하니 [zl] 박스에서 우클릭 → Reference로 전체 모드를 한 번 훑어보는 것을 추천합니다.
이 에피소드가 도움이 됐다면 눌러주세요.
공식 문서 참조
YouTube
채널에서 더 많은 Max/MSP 예제를 이어서 보세요
튜토리얼의 흐름을 끊지 않고, 실제 영상 데모와 채널 콘텐츠를 연속해서 확인할 수 있습니다.