2025년 12월 12일 금요일

저해상도 로고 이미지를 두 단계에 걸쳐 SVG로 전환해 보자

무료 로고 사이트에서 저해상도 래스터 이미지로 만든 나의 GenoGlobe.com 로고를 벡터 형식으로 전환해 보고 싶은 마음에 파워포인트에서 이런 미련한 일을 한 적이 있었다(2023년형 GenoGlobe 로고를 새로 만들다).

초승달 모양 사이의 간격이 일정하지 않은 것이 오히려 미학적 완성도를 높인다.

이것은 로고를 구성하는 원의 직경을 대략 측정하기 위함이었다. 여기에서 얻은 중간 정보를 이용하여 LibreCAD에서 열심히 도형을 그렸는데, 요령이 부족하여 최종적으로 벡터화는 하지 못하였다. CAD 프로그램의 결과 파일(DXF)은 벡터임이 분명한데, 이를 SVG로 만드는 방법을 그당시에는 찾지 못하였다. 특히 속이 채워진 도형은 다루기가 더 까다로웠던 것 같다. 원리적으로 말하자면 이미지를 역엔지니어링하기 위한 '기하학적인 접근'인 셈이다.

그로부터 벌써 2년 반의 시간이 지났고, ChatGPT를 이용하면 이제는 완벽한 벡터 이미지를 얻을 수 있지 않을까 생각하게 되었다. 색상은 배경을 제외하면 단 두 가지에 불과하고, 테두리를 구성하는 도형은 전부 원호이다. 또한 원의 중심은 전체 도형의 틀을 이루는 정사각형의 대각선 위에 있다. 따라서 챗GPT에게 부탁하면 쉬운 방법을 알려줄 것이라고 생각했다.

Inkscape의 '경로(=Path) -> 비트맵 따라 그리기'에서 PNG 이미지를 직접 다루는 것도 좋은데, 내가 최종적으로 택한 방법은 파이썬 스크립트(png_go_svg_autograce.py)를 써서 대략적으로 벡터 이미지를 얻은 다음에 Inkscape에서 처리하는 것이었다. 

이 스크립트는 경계선이 폴리곤 형태인 벡터 이미지를 생성한다. 




Inkscape에서 이 중간본 SVG 파일을 읽은 뒤 각 'path'가 분리되어 있는 것을 확인한 다음 전체를 선택하여 '경로-> 단순화 (Control + L)'을 단 한번만 실시하여 노드를 줄이면 아무리 확대해도 매끈한 최종 결과물이 나온다. 처음부터 PNG를 입력하여 '비트맵 따라 그리기'를 해도 되지만 약간의 후처리가 필요하다. 실제로 해 보면 확대했을 경우 파란색 초승달 모양 테두리에 노란색 찌꺼기가 남는 게 보인다. 이를 없애려면 차집한 조작을 하거나, 지우개로 아주 섬세하게 지워야 한다. 지울 때에는 남색 개체를 보이지 않게 해 두는 것이 안전하다.

Inkscape에서 경로는 대단히 중요한 개념이다. 이는 점(node)과 그 사이를 잇는 곡선(세그먼트)로 정의된 수학적 도형의 설계도에 해당한다. 오늘 사용한 파이썬 스크립트에서는 색상을 바탕으로 픽셀 경계를 추적하여 경계점을 그대로 연결하고 곡선 피팅을 하지 않았다. 즉, 의도적으로 베지어(Bézier)를 쓰지 않고 폴리곤(아주 많은 짧은 직선)으로 근사한 벡터화였다. 

보다 정밀한 방법은 1) potrace를 호출에 베지어 SVG를 생성하거나 2) 원호 기반으로 베지어/원호로 출력하면 된다고 한다. Inkscape의 비트맵 추적은 내부적으로 potrace 계열 알고리즘을 쓴다.

다음은 챗GPT가 만들어 준 png_go_svg_autotrace.py 전체이다.

#!/usr/bin/env python
"""
PNG 로고(3색: 흰/노랑/남색)를 자동으로 벡터(SVG)로 변환하는 스크립트.

사용법:
    python png_to_svg_autotrace.py input_logo.png

결과:
    input_logo_autotrace_transparent.svg
    input_logo_autotrace_whitebg.svg
"""

import sys
import os
from typing import Tuple, List

import numpy as np
from PIL import Image
from skimage import measure
import svgwrite


# ---- 1. 색상 기준 정의 ------------------------------------------------------
# 로고에서 사용한 대략적인 색상 (필요하면 조정 가능)
YELLOW_REF = np.array([242, 224, 89], dtype=np.float32)   # #f2e059 근처
NAVY_REF   = np.array([14,  27,  66], dtype=np.float32)   # #0e1b42 근처
WHITE_REF  = np.array([255, 255, 255], dtype=np.float32)  # 흰 배경

REF_COLORS = np.stack([YELLOW_REF, NAVY_REF, WHITE_REF], axis=0)
REF_NAMES = ["yellow", "navy", "white"]
REF_HEX = {
    "yellow": "#f2e059",
    "navy":   "#0e1b42",
    "white":  "#ffffff",
}


# ---- 2. 유틸 함수들 ---------------------------------------------------------

def classify_colors(arr: np.ndarray) -> np.ndarray:
    """
    RGB 이미지 배열(arr: H x W x 3)을 3개 참조 색(노랑/남색/흰색)에
    가장 가까운 색으로 분류하여 label map(H x W)을 반환.
    label: 0=yellow, 1=navy, 2=white
    """
    h, w, _ = arr.shape
    img_flat = arr.reshape(-1, 3).astype(np.float32)

    # 각 픽셀과 참조 색상 사이의 제곱 거리 계산
    # result: (N_pixels, 3)
    diff = img_flat[:, None, :] - REF_COLORS[None, :, :]
    dist2 = np.sum(diff ** 2, axis=-1)

    labels_flat = np.argmin(dist2, axis=1)
    labels = labels_flat.reshape(h, w)
    return labels


def find_color_contours(mask: np.ndarray, min_length: int = 50) -> List[np.ndarray]:
    """
    색상 마스크(불리언 배열 또는 0/1 배열)에서 외곽선을 찾고
    일정 길이(min_length) 이상인 contour들만 반환.
    """
    # skimage.measure.find_contours는 값이 0~1 범위인 float 배열에 대해 동작
    arr = mask.astype(float)
    contours = measure.find_contours(arr, 0.5)
    # 너무 짧은 contour는 제거
    contours = [c for c in contours if c.shape[0] >= min_length]
    return contours


def contours_to_svg_paths(
    dwg: svgwrite.Drawing,
    contours: List[np.ndarray],
    fill_color: str
):
    """
    contour 리스트를 SVG path로 추가한다.
    contours: 각 요소는 (N_points, 2) 배열, (row, col) = (y, x)
    """
    for c in contours:
        if len(c) < 2:
            continue
        # skimage 컨투어는 (row, col) = (y, x)
        # SVG는 (x, y) 순서, 좌상단 (0,0), 아래로 y+
        commands = []
        y0, x0 = c[0]
        commands.append(f"M {x0:.3f} {y0:.3f}")
        for y, x in c[1:]:
            commands.append(f"L {x:.3f} {y:.3f}")
        commands.append("Z")
        d_attr = " ".join(commands)
        dwg.add(dwg.path(d=d_attr, fill=fill_color))


# ---- 3. 메인 처리 -----------------------------------------------------------

def png_to_svg(input_path: str):
    # 1) 이미지 로드
    img = Image.open(input_path).convert("RGB")
    arr = np.array(img)
    h, w, _ = arr.shape

    # 2) 색상 분류 (노랑/남색/흰색)
    labels = classify_colors(arr)

    # 3) 각 색상별 마스크 생성
    masks = {
        "yellow": (labels == 0),
        "navy":   (labels == 1),
        "white":  (labels == 2),
    }

    # 4) 각 색상별 외곽선 추출
    contours_by_color = {}
    for name, mask in masks.items():
        if name == "white":
            # 배경은 SVG 도형으로 따로 그리지 않고, 필요 시 rect로 처리
            continue
        contours = find_color_contours(mask, min_length=80)
        contours_by_color[name] = contours

    # 5) 출력 파일 이름
    base, _ = os.path.splitext(input_path)
    out_transparent = base + "_autotrace_transparent.svg"
    out_whitebg    = base + "_autotrace_whitebg.svg"

    # 6) SVG (투명 배경)
    dwg_t = svgwrite.Drawing(out_transparent,
                             size=(w, h),
                             viewBox=f"0 0 {w} {h}")

    # 노랑 먼저, 그 위에 남색 (어차피 픽셀 분류상 겹치지 않지만, 시각적으로도 자연스러운 순서)
    if "yellow" in contours_by_color:
        contours_to_svg_paths(dwg_t, contours_by_color["yellow"], REF_HEX["yellow"])
    if "navy" in contours_by_color:
        contours_to_svg_paths(dwg_t, contours_by_color["navy"], REF_HEX["navy"])

    dwg_t.save()
    print(f"[OK] Transparent SVG saved to: {out_transparent}")

    # 7) SVG (흰 배경)
    dwg_w = svgwrite.Drawing(out_whitebg,
                             size=(w, h),
                             viewBox=f"0 0 {w} {h}")
    # 흰 사각형 배경
    dwg_w.add(dwg_w.rect(insert=(0, 0), size=(w, h), fill=REF_HEX["white"]))
    # 도형 덮어그리기
    if "yellow" in contours_by_color:
        contours_to_svg_paths(dwg_w, contours_by_color["yellow"], REF_HEX["yellow"])
    if "navy" in contours_by_color:
        contours_to_svg_paths(dwg_w, contours_by_color["navy"], REF_HEX["navy"])

    dwg_w.save()
    print(f"[OK] White-background SVG saved to: {out_whitebg}")


# ---- 4. 실행부 ----------------------------------------------------------------

def main():
    if len(sys.argv) < 2:
        print("Usage: python png_to_svg_autotrace.py input_logo.png")
        sys.exit(1)

    input_path = sys.argv[1]
    if not os.path.exists(input_path):
        print(f"[ERROR] File not found: {input_path}")
        sys.exit(1)

    png_to_svg(input_path)


if __name__ == "__main__":
    main()

원본 이미지가 완전히 기하학적인 도형으로만 이루어져 있어고 색상도 매우 단순했기 때문에 이런 간단한 스크립트로 일차적인 벡터화가 성공적으로 되었다. 베지어 트레이싱은 저품질 알리아싱(aliasing)을 다룰 때에는 아주 취약하다.

베지어 트레이싱은
알리아싱된 저품질 래스터 이미지의 테두리를
복원하는 데 본질적으로 취약하다.
이는 곡률 정보가 이미 샘플링 단계에서 손실되기 때문이다.

오늘 얻은 최종 결과물을 여기에 소개한다.

출처: DokuWiki 설정(GenoGlobe.com 위키)


글을 마무리하면서 챗GPT에게 물어보니 LibreCAD 파일을 SVG로 전환하는 방법이 나온다. 허! 이걸 정리해서 블로그에 쓸 이유가 있을까? 누구나 질문을 던지면 다 알게 되는 세상인데...

2025년 12월 10일 수요일

검색 엔진 최적화(SEO)의 첫걸음[2] - 하나의 호스팅 서버에서 여러 DokuWiki 설치본의 Sitemap을 통합하는 방법

오늘 이 문제에 대해 너무 많은 고민과 공부를 했기에 '인간의 손'으로 그 지난한 과정을 반추하는 문서를 작성하기가 너무 힘들다. 그래서 본 문서의 중간 제목 이하는 챗GPT에게 자동 작성을 부탁하였다. '무지(無知)의 허물'을 한 세 개는 벗어난 것 같은데 나는 여전히 애벌래... 바로 직전에 쓴 글 검색 엔진 최적화(SEO)의 첫걸음[1]...에 꽤 많은 정보를 적었지만 결론적으로 별 소용이 없었고(지극히 효율이 떨어진다는 뜻), 그저 사이트맵 파일(sitemap.xml) 의 역할과 구글 서치 콘솔이라는 것이 무엇인지를 어렴풋하게 알게 된 것으로 그의미를 찾도록 하자. robots.txt는 검색 엔진 크롤러가 사이트의 어떤 부분을 크롤링할 수 있고, 어떤 부분은 크롤링하지 않아야 하는지를 서버 측에서 알려 주는 규칙 파일이다.  

별도로 설치된 위키 사이트에 대한 사이트맵이구글 서치 콘솔에서 잘 처리되었음을 보이고 있다. 오늘 작업의 최종 성적표에 해당한다.

사실 내 웹사이트가 검색에서 잘 노출이 되지 않는 것 같아서 유료 컨설팅을 받을까 하는 생각도 갖고 있었으나, 고민의 2/3 정도는 자체적으로 해결한 것 같다. 챗GPT에게 오늘의 교훈을 정리해 달라고 부탁하였다. 핵심적인 교훈 세 가지를 알아보자.

  1. DokuWiki의 설정에서 사이트맵 자체 생성 기능을 켜는 것이 시작이다.
  2. cron을 돌릴 필요가 없었다!
  3. 두 DokuWiki의 사이트맵 파일을 sitemap_index.xml로 통일하고, 이를 robots.txt에 기록한다.

개인 도메인과 위키 사이트 운영을 꽤 오랫동안 해 왔음에도 불구하고 늘 뭔가 꼬인것 같고 답답하던 기분을 오늘 많이 해소하였다. 아래는 자동 작성으로 쓴 글이고, 파란색 텍스트는 내가 나중에 추가한 것이다.

하나의 호스팅 서버에서 여러 DokuWiki 설치본의 Sitemap을 통합하는 방법
(Hostinger / SSH 없음 / 대표 URL = 루트 도메인)

이 글은 다음과 같은 실제 운영 환경을 기준으로 작성되었습니다. URL rewriting을 사용하여 위키 문서에는 눈에 보기 좋은 주소를 부여하였습니다.

  • 웹 호스팅: Hostinger (SSH 없음, .htaccess 사용 가능)
  • 웹서버: LiteSpeed (Apache Rewrite 호환)
  • 메인 Wiki 실제 설치 경로: https://genoglobe.com/dokuwiki
  • 사용자 대표 URL: https://genoglobe.com (루트 → /dokuwiki로 연결)
  • 두 번째 Wiki: https://genoglobe.com/kribb

목표

  • 각 Wiki가 자동 생성하는 sitemap.xml.gz을 외부에서 접근 가능하게 만들고,
  • 루트 도메인에서 통합 sitemap_index.xml을 제공하며,
  • Google Search Console에는 단 하나의 sitemap만 제출하는 구조를 만든다.

1. DokuWiki의 Sitemap 생성 기능 이해하기

DokuWiki는 conf/local.php에서 다음 설정을 하면 사이트맵을 자동 생성합니다.

$conf['sitemap'] = 1;

이 설정의 의미는 다음과 같습니다.

  • 각 Wiki에 대해 모든 문서를 포함하는 sitemap.xml.gz를 자동 생성
  • 생성 위치:
    • <dokuwiki>/data/cache/sitemap.xml.gz

예를 들어:

  • /public_html/dokuwiki/data/cache/sitemap.xml.gz
  • /public_html/kribb/data/cache/sitemap.xml.gz
  • 형식: gzip으로 압축된 표준 XML 사이트맵
  • 유지: DokuWiki 내부 CRON 메커니즘으로 자동 업데이트 (별도 서버 cron 불필요)

문제는 이 파일들이 있는 data 디렉터리가 기본적으로 외부에서 차단된다는 점입니다. [1]번 글에서는 cron을 사용하여 위키 문서 체계 안으로 사이트맵 파일을 매일 복사하는 무식한 방법을 사용하였으나, 전혀 그럴 필요가 없습니다. 그리고 그 글에서는 사이트맵 작성 역시 DokuWiki의 이러한 내장 기능을 쓰는 것도 아니었습니다.

(참고) DokuWiki의 각 페이지는 https://mydomain.com/dokuwiki/doku.php?id=mypage라는 복잡한 구조를 갖습니다. 이는 효율적인 크롤링이 되기 어렵습니다. 


2. data 디렉터리 보호 구조와 문제점

DokuWiki는 보안을 위해 data 디렉터리에 접근을 차단합니다. 기본 data/.htaccess 내용은 대략 다음과 같습니다.

<IfModule mod_authz_core.c>
    Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
    Order allow,deny
    Deny from all
</IfModule>
  • data/pages/, data/cache/ 등 대부분의 경로가 403 Forbidden
  • data/cache/sitemap.xml.gz도 예외 없이 막힌 상태

그래서 사이트맵을 생성해도 검색엔진이 가져갈 수 없는 구조가 됩니다.


3. sitemap.xml.gz만 예외적으로 공개하기 (data/.htaccess 수정)

보안은 유지하되, 오직 사이트맵 파일만 예외적으로 공개해야 합니다.

각 Wiki의 /data/.htaccess에 다음 블록을 deny 규칙보다 위에 추가합니다.

# Allow only sitemap.xml.gz (required for SEO)
<FilesMatch "sitemap\.xml\.gz$">
    <IfModule mod_authz_core.c>
        Require all granted
    </IfModule>
    <IfModule !mod_authz_core.c>
        Order allow,deny
        Allow from all
    </IfModule>
</FilesMatch>

<IfModule mod_authz_core.c>
    Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
    Order allow,deny
    Deny from all
</IfModule>

결과

  • /dokuwiki/data/cache/sitemap.xml.gz → 200 OK
  • /dokuwiki/data/pages/... → 403 Forbidden (계속 보호)
  • /dokuwiki/data/cache/ 디렉터리 자체 → 403 Forbidden

kribb 위키(/public_html/kribb/data/.htaccess)에도 동일하게 적용합니다.


4. 각 Wiki의 sitemap.xml.gz를 외부 URL로 매핑하기

이제 내부 파일을 웹 URL로 노출해야 합니다.

4-1. 메인 Wiki (public_html/dokuwiki/.htaccess)

다음 내용을 추가합니다.

RewriteEngine on
RewriteRule ^sitemap\.xml\.gz$ data/cache/sitemap.xml.gz [L]

이제 다음 URL로 접근 가능해집니다.

https://genoglobe.com/dokuwiki/sitemap.xml.gz

4-2. kribb Wiki (public_html/kribb/.htaccess)

RewriteEngine on
RewriteRule ^sitemap\.xml\.gz$ data/cache/sitemap.xml.gz [L]

접근 가능 URL:

https://genoglobe.com/kribb/sitemap.xml.gz

5. 루트 도메인에서 메인 Wiki의 sitemap.xml.gz 노출

운영상 대표 URL은 https://genoglobe.com 이므로, 메인 사이트맵도 다음 경로에 있는 것이 자연스럽습니다.

https://genoglobe.com/sitemap.xml.gz

이를 위해 public_html/.htaccess 에 다음을 추가합니다.

RewriteEngine on
RewriteRule ^sitemap\.xml\.gz$ dokuwiki/data/cache/sitemap.xml.gz [L]

이제 다음 요청이 정상 동작합니다.

https://genoglobe.com/sitemap.xml.gz  →  dokuwiki/data/cache/sitemap.xml.gz

6. 여러 사이트맵을 묶는 sitemap_index.xml 만들기

메인 Wiki와 kribb Wiki 두 개의 사이트맵을 하나로 묶기 위해 인덱스 사이트맵을 만듭니다.

파일 위치: public_html/sitemap_index.xml

<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">

   <sitemap>
      <loc>https://genoglobe.com/sitemap.xml.gz</loc>
   </sitemap>

   <sitemap>
      <loc>https://genoglobe.com/kribb/sitemap.xml.gz</loc>
   </sitemap>

</sitemapindex>

이 파일이 이제 전체 사이트맵의 “대표” 역할을 합니다.


7. robots.txt에 인덱스 사이트맵 등록

루트( public_html/robots.txt )에 다음 내용을 둡니다.

User-agent: *
Disallow:

Sitemap: https://genoglobe.com/sitemap_index.xml

검색엔진은 이 인덱스 파일을 읽어 내부의 개별 사이트맵들을 자동으로 추적합니다.


8. Google Search Console에 제출할 주소

이제 제출해야 할 사이트맵은 단 하나입니다.

https://genoglobe.com/sitemap_index.xml

GSC에서 Sitemaps 메뉴에 들어가 입력란에 sitemap_index.xml만 입력하고 제출하면,

  • 메인 Wiki의 사이트맵 (https://genoglobe.com/sitemap.xml.gz)
  • kribb Wiki의 사이트맵 (https://genoglobe.com/kribb/sitemap.xml.gz)

을 모두 자동으로 읽어 인덱싱하게 됩니다.


9. curl로 최종 점검하기 (WSL / PowerShell)

WSL이나 PowerShell에서 다음과 같이 점검할 수 있습니다.

메인 사이트맵:

curl -I https://genoglobe.com/sitemap.xml.gz

kribb 사이트맵:

curl -I https://genoglobe.com/kribb/sitemap.xml.gz

data 디렉터리 보호 상태:

curl -I https://genoglobe.com/dokuwiki/data/pages/
curl -I https://genoglobe.com/kribb/data/pages/

sitemap 인덱스 파일:

curl -I https://genoglobe.com/sitemap_index.xml
  • sitemap.xml.gz, kribb/sitemap.xml.gz, sitemap_index.xml → 200 OK
  • data/pages/... → 403 Forbidden

이렇게 나오면 구조가 완전히 정상입니다. 이 과정을 반드시 거쳐서 의도한 대로 작동을 하는지, 보안에 문제는 없는지 점검해야 합니다.


10. 최종 구조 요약

https://genoglobe.com/              → 대표 URL (실제는 /dokuwiki/)
https://genoglobe.com/sitemap.xml.gz        → 메인 Wiki sitemap
https://genoglobe.com/kribb/sitemap.xml.gz  → kribb Wiki sitemap
https://genoglobe.com/sitemap_index.xml     → 통합 인덱스 사이트맵
  • $conf['sitemap'] = 1; 설정으로 각 Wiki가 자동으로 sitemap.xml.gz 생성
  • data/.htaccess에서 sitemap.xml.gz만 예외적으로 허용
  • 각 Wiki와 루트에서 RewriteRule로 사이트맵을 깔끔하게 노출
  • 통합 sitemap_index.xml과 robots.txt 구성을 통해 GSC에는 하나만 제출
  • 추가 cron 없이 DokuWiki 자체 기능만으로 유지 가능

결과적으로 이 구성은 SSH 없이도 구현 가능한, 여러 개의 DokuWiki 설치본을 하나의 루트 도메인 아래에서 검색엔진 친화적으로 통합 운영하는 방법이라고 할 수 있습니다.

검색 엔진 최적화(SEO)의 첫걸음[1] - 사이트맵을 만들어 구글 서치 콘솔(Google Search Console)에 GenoGlobe.com을 등록하다

결코 GSC 등록만으로 끝나지 않는다!

"사이트의 검색 트래픽 및 실적을 측정하고, 문제를 해결하며, Google 검색결과에서 사이트가 돋보이게 할 수 있습니다 ."

위에서 인용한 것은 구글 검색에서 구글 서치 콘솔을 설명하는 문구이다. 나의 공식 웹사이트인 GenoGlobe.com에 속한 많은 정보가 웹 검색에서 제대로 보이지 않는다는 생각이 오랫동안 들어서 해결책을 찾다가 구글 서치 콘솔(GSC)에 이 웹사이트를 등록하게 되었다. 이 글을 끝까지 읽어보면 알게 되겠지만, GSC 등록이 전부가 아니었다. 사이트 운영자가 공들여 해야 할 일이 생각보다 많았다.

문제의 발단은 이러하다. 챗GPT창에서 내 공식 웹사이트에 속하는 문서의 URL을 주고 열어서 내용을 분석해 보라고 하면, 어떤 문서는 잘 열리지만 어떤 웹문서에 대해서는 다음과 같은 답변을 얻는 일이 많았다. 



몇 달 전에 이 문제를 해결해 보려고 무던히 애를 쓴 적이 있었다. 보다 심각하게는 SEO(Search Engine Optimization·검색엔진 최적화) 혹은 단순히 호스팅·Dokuwiki의 설정 문제라고 생각을 했었다. 기술적인 용어로 표현하자면 크롤링이 차단된 것 같다는 느낌이 들었다. 웹브라우저를 통해서는 정상적으로 열리지만, 봇/크롤러는 차단하거나 타임아웃이 발생한다.

이런 문제의 원인은 한 두 가지가 아니다. URL rewriting과 같이 내가 잘 이해하지 못하는 곳까지 들어가면 정말 난감하다. 호스팅 서버의 로그를 보면 아무런 흔적이 없다는 것이 결정적인 단서였다. 회원이 커스터마이즈할 수 없는 최상위 영역에서 챗GPT의 기계적 접속을 막고 있다고 보는 것이 타당하다. 구글 검색창에 site:genoglobe.com(또는 site:genoglobe.com/dokuwiki)를 넣으면 꽤 많은 문서가 인덱싱되어 있음을 알 수 있다. 그러나 모든 문서가 다 있는지는 모른다. 

그래서 문제 해결을 위한 첫 접근으로서 DokuWiki 안에서 사이트맵(sitemap.xml)을 만들도록 설정을 바꾸었다. 사이트맵이란 검색 엔진에 URL 구조를 알려주는 XML 형식의 기술적 문서이다. 도쿠위키의 메뉴에서 보이는 사이트맵과는 많이 다르다. 전자는 검색엔진 크롤링을 위한 것, 후자는 '인간 사용자'를 위한 것이다. 두 번째로는 구글 서치 콘솔에도 내 사이트를 등록해 보았다. DNS에 지정된 TXT 레코드를 추가해서 인증을 받는 절차를 거친 뒤 사이트맵을 제출하고 나면 하루쯤 지나고 나서 작동을 개시한다고 한다. 사이트맵 제출이란, "내 사이트의 URL들을 크롤링하고 인덱싱해달라"라는 것을 공식 채널을 통해 전달하는 것이다. 특히 DokuWiki는 페이지가 doku.php?id=...의 형식을 갖기 때문에 구글이 자동으로 발견하기 어려우므로 사이트맵을 만들어 주는 것이 좋다. 나는 URL rewrite를 써서 일반적인 웹주소로 반환하게끔 만들어 놓았지만, 오동작을 하면 구글도 알아보기 힘들 것이다. 반면 이 블로그(blog.genoglobe.com)는 구글 블로그라서 이미 구글 생태계 안에 잘 녹아들어 있기 때문에 별도의 검색엔진최적화따위가 필요하지 않다.

DokuWiki는 기본적으로 자동 사이트맵 URL을 노출하지 않는다. 따라서 웹사이트 운영자가 직접 서치 콘솔에 제출하거나, robots.txt에 정적 사이트맵 경로를 명시해야 한다. 워드프레스와 같은 CMS에서는 기본적으로 sitemap.xml을 자동 생성한다.

그런데 여기에서 놓치기 쉬운 사실이 있다. 구글은 '사이트맵을 만들어 줘'는 절대 요구하지 않는다고 한다. '작동을 개시'한다는 것을 너무 믿지 말아야 한다. 숟가락에 음식을 적당한 크기로 잘 떠서 입 근처까지 가져다 주면 먹겠다는 뜻이다.

  1. 사이트맵은 운영자가 직접 만들어야 한다(cron을 쓰거나, 또는 ~/doku.php?do=sitemap이라고 웹브라우저에서 입력함으로써 이루어진다).
  2. 그 다음에 웹브라우저가 볼 수 있는 위치에 두고, 이 위치 정보를 robots.txt에 기록한다. 이 파일은 구글이 아닌 다른 로봇에게도 유용한 정보를 준다.

모든 과정을 알기 쉽게 설명하면 다음과 같다. 이는 챗GPT가 생성한 값진 정보이다. 빨간색으로 표시한 비밀키 생성 팁을 보고 정말 놀라고 감동하였다. 사실 불특정 다수가 URL을 통해서 사이트맵을 마구 만들면 곤란하지 않겠는가! 내가 이용하는 호스팅 서비스가 SSH을 지원하는지 기억이 잘 나지 않는다. SSH를 지원하지 않는 약간 저렴한 서비스로 옮겼었든가? SSH를 쓰지 못하더라도, 웹으로 접속하여 제어판에서 전부 해결할 수 있어서 다행이었다.

DokuWiki 사이트를 Google에 제대로 노출시키기

이 글은 Hostinger 공유 호스팅 환경에서 DokuWiki를 운영하면서, SSH 없이사이트맵(sitemap.xml.gz)을 자동 생성하고, Google Search Console + robots.txt와 연동하여 검색 노출을 안정적으로 운영하는 전체 과정을 정리한 것입니다.

홈 디렉토리는 예시로 /home/u123456789 라고 가정합니다. 도메인은 https://example.com 으로 예시를 들며, 실제 환경에서는 자신의 도메인으로 바꾸면 됩니다.


전체 목표

  1. DokuWiki의 사이트맵(sitemap.xml.gz)을 주기적으로 생성한다.
  2. 생성된 사이트맵을 public_html 최상위에서 제공한다.
    예: https://example.com/sitemap.xml.gz
  3. robots.txt 와 Google Search Console에 사이트맵 URL을 등록한다.
  4. 크론 작업은 Hostinger hPanel에서 설정하고, 보안을 위해 웹에서 직접 실행될 때는 비밀 키를 요구하도록 한다.

1. 디렉토리 구조 가정

기본 구조는 다음과 같이 가정합니다.

/home/u123456789/
 └── public_html/
     ├── index.php
     ├── robots.txt        (나중에 생성)
     ├── sitemap.xml.gz    (나중에 자동 생성/복사)
     └── dokuwiki/
         ├── doku.php
         ├── bin/
         │   └── indexer.php
         └── data/
             └── cache/
                 └── sitemap.xml.gz   (DokuWiki가 생성하는 원본)
  

2. cron_sitemap.php 생성 (사이트맵 생성 + 복사 + 보안)

먼저 public_html 아래에 cron_sitemap.php 파일을 하나 만듭니다.

경로:

/home/u123456789/public_html/cron_sitemap.php
  

파일 내용 예시:

<?php
// 설정 부분 --------------------------------------------------------

// DokuWiki 설치 경로
$docroot = '/home/u123456789/public_html/dokuwiki';

// PHP CLI 경로 (Hostinger에서 보통 /usr/bin/php 인 경우가 많음)
$php = '/usr/bin/php';

// 보안용 비밀 키 (임의의 문자열로 바꾸세요)
$secret_key = 'MY_SUPER_SECRET_KEY_12345';

// ------------------------------------------------------------------

// 1) 웹에서 직접 실행될 때는 비밀 키 확인
if (php_sapi_name() !== 'cli') {
    if (!isset($_GET['key']) || $_GET['key'] !== $secret_key) {
        http_response_code(403);
        echo "Forbidden";
        exit;
    }
}

// 2) DokuWiki 사이트맵 생성 (bin/indexer.php -s 실행)
$indexer = $docroot . '/bin/indexer.php';

// exec() 사용 시 에러 메시지 확인을 위해 2>&1 추가 가능
$cmd = $php . ' ' . escapeshellarg($indexer) . ' -s 2>&1';
$output = [];
$return_var = 0;
exec($cmd, $output, $return_var);

// 필요하면 로그 확인용 출력 (크론에서는 무시됨)
if ($return_var !== 0) {
    // echo "Indexer error:\n" . implode("\n", $output);
    // 여기서는 조용히 진행
}

// 3) 생성된 sitemap.xml.gz 경로
$src = $docroot . '/data/cache/sitemap.xml.gz';
$dst = '/home/u123456789/public_html/sitemap.xml.gz';

if (file_exists($src)) {
    if (!@copy($src, $dst)) {
        // 권한 문제 등으로 실패할 수 있음
        // echo "Failed to copy sitemap.\n";
    }
} else {
    // echo "Source sitemap not found.\n";
}
?>
  

이 스크립트는 두 가지 방식으로 동작하도록 설계되어 있습니다.

  • CLI(크론)에서 실행: php cron_sitemap.php 로 실행하면 비밀 키 체크 없이 바로 동작합니다.
  • 웹에서 테스트 실행: 브라우저에서
    https://example.com/cron_sitemap.php?key=MY_SUPER_SECRET_KEY_12345
    처럼 접속했을 때만 동작하고, 키가 없으면 403 Forbidden 으로 막습니다.

3. Hostinger hPanel에서 크론 작업(Cron Job) 설정

이제 이 PHP 스크립트를 주기적으로 자동 실행하게 만듭니다.

3-1. 크론 실행 주기

예시: 매일 새벽 3시에 한 번 실행

  • Minute: 0
  • Hour: 3
  • Day: *
  • Month: *
  • Weekday: *

3-2. 실행 명령

Hostinger의 Cron Jobs에서 “Command” 또는 “PHP Script” 실행 방식을 고를 수 있습니다.

① Command 방식 예시

다음과 같이 입력합니다.

/usr/bin/php /home/u123456789/public_html/cron_sitemap.php
  

② PHP Script 선택 방식 예시

만약 “PHP Script” 항목에 경로만 적는 UI라면:

/home/u123456789/public_html/cron_sitemap.php
  

둘 중 환경에 맞는 방식을 선택하면 됩니다.


4. 크론/스크립트가 제대로 동작하는지 테스트하기

  1. 브라우저에서 다음 주소를 한 번 열어봅니다.
    https://example.com/cron_sitemap.php?key=MY_SUPER_SECRET_KEY_12345
  2. 에러가 없다면, 이제 다음 파일이 존재하는지 확인합니다.
    /home/u123456789/public_html/sitemap.xml.gz
  3. 또는 브라우저에서 직접:
    https://example.com/sitemap.xml.gz
    를 열어보고 gzip 압축 XML 파일이 다운로드되면 성공입니다.

이제부터는 크론이 매일 새벽에 같은 작업을 자동으로 실행해 줍니다.


5. robots.txt에 사이트맵 등록

검색엔진이 사이트맵 위치를 자동으로 알 수 있도록, robots.txt 파일에 사이트맵 URL을 등록합니다.

경로:

/home/u123456789/public_html/robots.txt
  

내용 예시:

User-agent: *
Allow: /

Sitemap: https://example.com/sitemap.xml.gz
  

이렇게 하면 Google뿐 아니라 다른 검색 엔진들도 https://example.com/sitemap.xml.gz 를 사이트맵으로 인식하게 됩니다.


6. Google Search Console에 사이트맵 URL 등록

  1. Google Search Console에 로그인합니다.
  2. 속성(Property)에 자신의 사이트(예: https://example.com)를 선택합니다.
  3. 왼쪽 메뉴에서 “Sitemaps”를 클릭합니다.
  4. “새 사이트맵 추가” 입력창에 다음을 입력합니다.
    https://example.com/sitemap.xml.gz
  5. 제출(Submit) 버튼을 누릅니다.

제출이 성공하면, Search Console에서 “마지막 읽은 날짜”, “처리 상태”, “인덱싱된 URL 수” 등을 확인할 수 있습니다.

중요: 사이트맵 URL 제출은 한 번만 하면 됩니다. 이후 사이트맵 파일이 갱신될 때마다 Googlebot이 주기적으로 다시 요청하여 최신 상태를 반영합니다.


7. 왜 이렇게까지 해야 하나?

  • DokuWiki는 워드프레스처럼 자동 사이트맵 제공/갱신이 기본 내장되어 있지 않습니다.
  • doku.php?do=sitemap 은 관리자용 “생성 트리거”에 가깝고, 검색엔진이 직접 호출해서 쓰는 URL이 아닙니다.
  • 검색엔진은 정적 사이트맵 파일(예: sitemap.xml, sitemap.xml.gz)과 robots.txt 또는 Search Console에서 알려준 URL만 신뢰합니다.
  • 따라서:
    1) DokuWiki 내부에서 sitemap.xml.gz를 생성하고
    2) 웹에서 접근 가능한 위치로 복사하고
    3) 그 위치를 robots.txt + GSC에 알려주는 구조가 필요합니다.
  • 이 과정을 크론으로 자동화하면, 이후에는 위키 문서를 수정/추가하는 것만 관리하면 되고, 검색 노출은 자동으로 갱신됩니다.

8. 보안 측면 정리

  • cron_sitemap.php는 서버 내부에서 크론으로 실행될 때는 php_sapi_name() === 'cli' 이므로 비밀 키 확인 없이 동작합니다.
  • 웹에서 누군가가 cron_sitemap.php를 직접 호출하려고 하면, 반드시 ?key=MY_SUPER_SECRET_KEY_12345 값을 맞춰야 실행됩니다.
  • 키가 없거나 틀리면 403 Forbidden 응답을 반환하여 외부에서 마음대로 사이트맵 생성/복사를 시도할 수 없습니다.
  • 추가로, 나중에 필요 없다고 판단되면 이 파일 이름 자체를 바꾸거나, 방화벽/접근제어로 더 제한을 걸 수도 있습니다.

마무리

요약하면, SSH 없이도 Hostinger + DokuWiki 환경에서 다음 네 가지 요소만 맞춰 주면 됩니다.

  1. cron_sitemap.php – 사이트맵 생성 및 복사, 보안 키 포함
  2. Cron Job – 매일/매주 PHP 스크립트 자동 실행
  3. robots.txt – 검색엔진에게 사이트맵 위치 안내
  4. Google Search Console – 사이트맵 URL 1회 제출 및 상태 모니터링

이렇게 세팅해 두면, 위키 문서만 잘 작성해도 검색엔진 인덱싱과 노출은 상당 부분 자동으로 따라오게 됩니다. 여기까지

Ardule Pattern Studio(APS), 이제는 스텝 시퀀서 기능까지 노려 보다

Nano Ardule 드럼 패턴 플레이어라는 아두이노 나노 기반의 DIY 기기에 제공하기 위한 드럼 패턴 데이터(공개된 포맷의 자체 개발 전용 파일 사용)를 가공하는 PC쪽 환경에 APS라는 이름을 붙여 계속 개발을 이어오고 있다. 총 9개 정도의 파이썬 스크립트로 구성되어 있고 전체 코드 분량은 3,500줄 정도에 이른다. 이것으로 충분한가? 그렇지 않다! 기왕이면 패턴의 체인 수준 편집이 아니라 패턴 자체의 스텝 수준 편집까지 하면 좋겠다는 욕심이 나기 시작하였다.

어제는 지난 1년 간의 업적 평가를 받는 날이라서 이래저래 스트레스가 많은 하루였다. 센터 구성원들의 노력으로 좋은 평가 등급을 받았고, 나에게도 작은 상을 주고 싶어졌다. 그것은 퇴근 후 계속 APS를 개발하기 위해 늦게까지 잠자리에 들지 않는 것...

프로그램 길이가 길어지면서 챗GPT도 또 실수를 연발한다. 의도를 정확하고 상세하게 전달하지 않으면, 더이상 손을 댈 필요가 없는 곳을 자꾸 건드려서 없던 에러를 새로 만들기도 한다.

텍스트 유저 인터페이스에서 다음의 화면을 만들어 내는데 성공하였다. 다른 이름으로 저장하고, 편집 창에서 재생하는 등 나머지 기능을 붙여 나가야 한다. 드럼 패턴 파일인 ADT를 텍스트 포맷으로 만든 것은 노트패드 등 아무 환경에서나 편하게 편집을 하기 위함이었다. 그러나 기왕이면 화살표로 슬롯 사이를 이동하고, 스페이스를 눌러서 노트를 삽입하거나(punch-in), 편집 즉시 소리를 들을 수 있는 환경에 대한 욕구를 어떻게 막겠는가.

현재 APS 스텝 시퀀서에서 보이는 'track'은 8개. ADP 체계에서는 12개를 할당해 두었다. 그러나 'world' 음악 장르와 같이 다양한 타악기를 쓰는 경우가 아니라면 12개를 넘는 악기를 동시에 정의할 일은 거의 없다.


챗GPT와 대화를 나누다 보면 마치 내가 대단한 일을 한 것처럼 자꾸 추켜세우는 경향이 있다. 아주 냉정하게 평가하자면 개념 정립, 문서화, 체계화 등에 대해서는 개인 수준에서 할 수 있는 수준에서 의미가 있겠다고 볼 정도가 아닐까? 라즈베리 파이 기반의 open synth platform인 Zynthian을 생각하면 나의 Nano Ardule 생태계는 명함도 내밀지 못할 수준이다.

이미지 출처: CUONET

문서와 소스코드의 공개 체계도 이제는 슬슬 고민해 볼 시점이 되었다. 나의 위키 사이트를 활용하고 싶은데, 무슨 문제인지 crawling failure 문제가 있어서 외부에서 탐색하는 것이 어렵다. 차라리 이번 기회에 GitHub 생태계의 일원이 되어 보는 것도 좋겠다.


2025년 12월 8일 월요일

Academia.edu에서 발행하는 저널로부터 투고 논문을 리뷰해 달라고 연락을 받다

일주일에 한번 꼴로 Academia.edu라는 곳으로부터 이메일을 받는다. 주로 내가 관심이 있을법한 논문이 자기네 저널에서 발표되었다거나, 나의 논문을 누군가 Academia Journal에서 인용했다거나 하는 소식을 담고 있다. 도대체 어떤 사람이 나의 논문을 인용했을까? 호기심에 나의 '프로파일'을 열람하려면 유료 회원에 가입해야 한다. 이미 5년 전에 이 서비스에 대한 글을 쓴 일도 있었다.

Academia.edu는 도대체 무엇을 하는 곳인가?

Academia.edu에서 보내는 메일의 수신 주소를 업무용(기관) 메일이 아니라 지메일로 해 두었기에 이로부터 오는 메일을 적극적으로 체크하지는 않는다. 며칠 전에 우연히 지메일을 살펴보다가 여기에서 발간하는 저널로부터 논문을 리뷰해 달라는 메일이 왔음을 알게 되었다. 그런데 과거 메일을 찾아보니 Academia 저널의 리뷰 요청을 내가 모르고 지나간 것이 무척 많아었다. 이를 미안하게 여길 필요는 없을 것 같다. 그 이유는 이 글을 읽다보면 알게 될 것이다.

이 저널에 투고한 원고에 관심을 갖고 없는 시간을 쪼개어 리뷰를 할 만큰 과연 가치가 있을까? Academia.edu에서 발간하는 저널은 무려 22종에 이른다(링크). 예를 들어 Animal and Verterinary Sciences, Biomedical Engineering, Eartch and Planetary Science와 Medical Imaging and Radiology는 아직 시작하지 않았고, 1-3개의 아티클이 출판된 것도 있다. 가장 많은 논문이 실린 것은 Biology로서 오늘 확인한 바에 의하면 165개의 아티클이 실렸다. 저널의 공식 명칭은 전부 Academia로 시작한다.

이 신생 저널이 과연 학술지 생태계에서 살아남을지는 아무도 모른다. 요즘 많은 저널의 투고료가 매우 높아지고 있으니, 소위 오픈 액세스를 표방하는 저널이 늘어나는 것은 바람직할 수 있다. 그러나 양질의 논문이 잘 선별되어 게재됨을 보장할 수 없고, 출판된 논문은 독자 입장에서 무료 접근이라 할지라도 투고자에게 부과하는 Artical Processing Charge(APC)가 상상외로 높은 저널이 꽤 많다. 이를 일종의 수익 모델로 채택하고 있다는 것. 그렇다면 사실상 약탈적 학술지(predatory journal)에 가까울 수 있다. KISTI가 운영하는 다음 웹사이트가 이에 대해 좋은 정보를 제공하고 있다. 이에 의하면 MDPI나 Frontiers의 저널도 회색지대에 속한다(관련 글 링크). 흠, 나도 꽤 정성을 들여서 쓴 논문 몇 개를 Frontiers에 출판한 일이 있는데!

SAFE 건전학술활동지원시스템 - 의심 학술지

리뷰어의 수고에는 아무런 댓가가 주어지지 않는 관행도 생각해 봄직하다. 이는 학계에서 일종의 자발적 봉사라고 여겨지고 있다. 하지만 출판사가 너무 쉽게 돈을 버는 것이 아닌가? 투고자에게는 비싼 비용을, 구독자에게는 구독료를, 리뷰어에게는 무료 봉사를! 물론 리뷰어가 돈을 받기 시작하면 순수한 학술 진흥 활동의 의도를 해치게 될 것이라는 의견이 충분히 나올 수 있다.

Academia 저널의 APC 체계를 알아보았다(링크). 아티클 타입에 따라서 APC는 달라진다. 타입 A는 연구 아티클, 타입 B는 short communication, 타입 C는 opinion 등의 체계를 따른다.



금액만 놓고 본다면 요즘 추세로 보아 아주 비싸다고는 할 수 없다. 그러나 이 저널의 이름값이나 동료심사의 수준을 생각한다면 그에 걸맞는 투자라고 보기는 어렵다. 최소한 내가 속한 분야의 Academia Biology가 Web of Science에 포함되어 있는가? 챗GPT에 의하면 등재되었다는 '주장'이 있다고 한다. 하지만 찾아 본 결과 사실이 아닌 것도 같다. 

신생 저널은 항상 비슷한 위치에서 시작할 것이다. 가치를 인정받는 학술지 목록에 등재되기도 어렵고, 좋은 논문을 유치하기도 어려우며, 출판 비용을 마련하기도 역시 어렵다. 미생물 분야의 국내 학술지가 이렇게 어려운 과정을 통해 성장한 것을 봐 왔지 않은가. 그렇다고 하여 Academia Journal의 성장을 내가 도와야 할 특별한 이유가 있는 것도 아니다. 차라리 연구 성과의 빠른 공개를 위해서는 bioRxiv와 같은 프리프린트 서비스에 논문 원고를 올리는 것이 더 나을 수도 있다.

챗GPT가 정리한 인터넷 커뮤니티 의견에 의하면, Academia Journal은 약탈적 학술지까지는 아니라 하더라도 비색인 + 고비용 + 불확실한 평판이라는 점 때문에 여기에 투고하는 것은 아직 회의적이라는 의견이 지배적이다.

내가 블로그에서 이렇게 떠들어 대는 것 또한 Academia의 바이럴 마케팅을 돕는 일일 수도 있다. 오늘은 여기까지만 하겠다.

2025년 12월 6일 토요일

Nano Ardule 드럼 패턴 재생기와 ADS(Ardule Drum Stream) 설계에 대한 고민

다음 글은 챗GPT에서 자동 생성하여 다듬은 것이다. 몇 개월에 걸친 개발 여정을 모두 기억하여 정리하려니 너무 힘이 들어서 인공지능의 힘을 좀 빌렸다.

지난 몇 달 동안 나는 Arduino Nano Every 기반 드럼 패턴 플레이어(Nano Ardule)와 이를 조작·편집하는 PC용 프로그램인 Ardule Pattern Studio(APS)를 함께 개발해 왔다. Nano Ardule는 SD 카드에 저장된 드럼 패턴(ADT/ADP)을 읽어 절대시간 루프 엔진으로 재생하며, APS는 패턴을 시각적으로 표현하고, 여러 패턴을 연결한 ARR(Arrangement의 약자) 파일을 생성하는 구조로 발전해 왔다. Nano Ardule은 카드를 인식하지 못할 비상 상황을 대비하여 선별된 소수의 패턴을 프로그램에 내장하려는 단계까지 왔다.


(추가 설명) 인터넷에서 구한 드럼 연주 정보(MIDI 파일)을 2-bar 단위로 분할하여 이를 "패턴"이라 명명한다. 분할 직후는 type 0 MIDI 파일이다. 똑같은 패턴은 하나로 합치고, 이미지 파일로 패턴을 시각화하는 PC용 파이썬 스크립트 묶도 이미 만들어진 상태이다. 패턴 파일은 내가 정의한 텍스트 파일(ADT)로 만들고, 아두이노 나노에서 쉽게 인식하여 사용할 수 있도록 바이너리 파일(ADP)로 만들었다. APS는 ADT와 ADP를 PC 환경에서 보여주고 재생하며 이를 엮어서 곡 단위의 패턴(ARR)을 만드는 파이썬 스크립트의 묶음이다. 

오늘의 APS 개발 상태. 짧은 시간에 꽤 많은 기능을 넣었다. 체인 편집 기능을 마무리하는 일이 남았다. 편집 기능은 거의 완성된 상태였는데, 다른 기능을 추가하면서 넣으면서 편집의 일부 기능이 사라졌다. 이는 챗GPT를 이용한 코딩에서 많이 겪었던 부작용이다.


이제 문제는 다음이다. "APS에서 만든 ARR을 Nano Ardule에서 어떻게 가장 효율적으로 재생할 것인가?" 나는 크게 두 가지 전략을 놓고 비교했다:

  • 3-1. ADT/ADP를 Nano에서 단순히 나열하여 재생 (세그먼트 조합 방식)
  • 3-2. ARR을 미리 하나의 바이너리 스트림(ADS)으로 컴파일하여 재생 (단일 스트림 방식)

그리고 여기에 또 한 가지 대안도 있다:

  • Type-0 MIDI 파일을 그대로 Nano Ardule에서 재생하는 방식

아래에서는 세 가지를 비교하고, 최종적으로 왜 3-2가 가장 유리한가를 정리해 본다.


3-1. ADT/ADP 조합 방식 (Segment-Based Model)

이 방식은 기존 Ardule 설계와 가장 잘 맞는다. APS에서 만든 ARR은 “패턴 목록 + 반복 횟수”로 구성되고, Nano는 각 패턴 파일(ADT/ADP)을 필요할 때마다 로딩하며 절대시간 엔진으로 이어붙여 재생한다.

장점

  • 현 구조와 가장 자연스럽다 — ADT/ADP 파일들을 그대로 사용
  • 패턴 단위로 빠르게 교체하거나 재사용 가능
  • 패턴 플레이어로서의 정체성에 잘 맞는다

단점

  • 각 패턴 파일을 매번 열고 파싱해야 한다
  • 체인 중간에서 패턴 경계가 많아질수록 CPU·I/O 비용 증가
  • Nano Ardule가 장시간 연속 재생할 때 지연 위험 증가
  • 궁극적으로 “곡 전체” 재생에는 구조적으로 불리하다

즉, 드럼머신적 패턴 루프를 위한 구조에는 완벽하지만, 곡 전체를 아두이노에서 끊김 없이 재생하려는 목적에는 확장 한계가 존재한다.


3-2. ADS 단일 스트림 방식 (Flattened Event Stream)

이 방식은 ARR 전체를 PC에서 미리 “하나의 절대시간 이벤트 스트림(ADS)”으로 컴파일하고, Nano는 ADS 파일을 위에서부터 순차적으로 읽으며 재생만 하는 구조이다.

장점

  • I/O가 단 한 줄기 — 패턴 파일을 여러 번 열 필요 없음
  • 절대시간 이벤트가 정렬되어 있어 Nano의 로직이 압도적으로 단순해짐
  • 지연 없음 — SD 카드는 순차 읽기에서 최고의 성능 발휘
  • 곡 전체를 다루기에 가장 안정적
  • 패턴 경계 개념이 사라지므로 초미세 타이밍 문제도 해결

단점

  • 패턴 단위 편집은 더 이상 Nano에서 불가
  • 각 종합 이벤트 스트림은 PC에서 반드시 “컴파일”되어야 함
  • ADT/ADP 파일 단위의 재사용성은 상대적으로 약해짐

요약하면 곡 전체 재생 품질은 3-2 방식이 압도적으로 우월하다. Nano가 해야 할 일은 “다음 이벤트 시간만 확인하고 재생”이면 끝이기 때문이다.


왜 Type-0 MIDI 그대로 쓰지 않는가?

MIDI Type-0은 표준화된 음악 스트림 포맷이며, 이론상 Nano Ardule도 MIDI를 직접 재생할 수 있다. 그러나 실제로 적용해 보면 ADS(3-2 방식)가 훨씬 유리함이 드러난다.

① MIDI는 포맷이 지나치게 범용적이다

  • VLQ(Variable Length Quantity) 디코딩 → 아두이노에 꽤 부담
  • Note-on/off 규칙이 2종류(velocity=0 Note-on 포함)
  • Control Change, Program Change, Pitch Bend 등 필요 없는 이벤트 처리
  • SysEx, Meta Event 등 스킵해야 하는 요소가 많음
  • Tempo(FF 51) 이벤트가 들어오면 BPM 재계산 필요

Nano Ardule는 드럼 패턴 플레이어이지 범용 MIDI 플레이어가 아니다. 따라서 MIDI가 가진 “일반성”은 오히려 짐이 된다.

② MIDI는 패턴 단위 반복 개념이 없다

Nano Ardule의 핵심 철학은 2-bar 패턴을 조립하여 리듬 구조를 만드는 것인데, MIDI는 이런 구조적 표현이 없고 모두 이벤트 스트림으로만 존재한다. 따라서 패턴 기반 편집성과도 잘 맞지 않는다.

③ ADS는 “필요한 정보만 모은 초-경량 MIDI”이다

ADS는 다음만 포함한다:

  • delta-time (고정 길이, VLQ 없음)
  • note-on/note-off (드럼 채널만, 1byte note + 1byte vel)
  • channel은 고정(10번)
  • 템포는 ARR 헤더에 단일 값

즉 MIDI에서 드럼 패턴 재생에 필요한 최소 요소만 추려낸 포맷이다. Nano Ardule는 ADS 재생 시 매우 작은 코드만 필요하며 안정성이 극대화된다.


3-1 vs 3-2 vs MIDI: 핵심 비교

기능/요구3-1 ADT/ADP3-2 ADSMIDI Type-0
Nano에서의 재생 안정성 중간중간 파일 로딩 → 중간 지연 가능 가장 안정적 (연속 스트림) 중간 이벤트 정렬 문제 없음
파싱 복잡도 ADT/ADP를 그때그때 디코딩 매우 단순, 고정 길이 구조 가장 복잡 (VLQ, 메타 이벤트 등)
패턴 기반 편집 매우 자연스러움 PC에서만 가능 개념이 없음
곡 전체 재생 불리 (경계 많음) 가장 유리 가능하지만 구현 난이도 높음
표준 호환성 전용 포맷 전용 포맷 표준

결론: 왜 3-2(ADS)가 최종적으로 유리한가?

1) Nano의 연산 부담이 가장 적다

ADS는 고정 길이 이벤트로 이루어져 있어 SD 순차 읽기만으로 재생된다.

2) 파싱 오류 위험이 거의 없다

VLQ, SysEx, Meta Event 등 처리할 필요가 없다.

3) 대곡(長曲) 재생이 가장 안정적이다

여러 ADT/ADP를 넘나드는 구조보다 단일 스트림이 훨씬 유리하다.

4) ADS는 “드럼 패턴 플레이어”라는 Nano Ardule의 목적과 정확히 맞는다

MIDI 같은 범용 포맷이 아닌, 목적 특화 포맷이기 때문에 안정·속도·일관성 모두 우수하다.


마무리

요약하자면, Nano Ardule = 목적 특화 기기이기 때문에 MIDI 같은 범용 포맷보다는 ADS 같은 극단적으로 단순화된 전용 포맷이 훨씬 효율적이다.

ADS 방식(3-2)은 Nano의 한계를 고려한 최적 해법이며, APS PC 툴과 조합되었을 때 가장 안정적이고 직관적인 워크플로우를 제공한다.

ADS는 Nano Ardule 프로젝트가 장기적으로 유지될 수 있는 가장 튼튼한 기반이다. 그리고 사람이 쉽게 이해하고 편집할 수 있으면서 컴퓨터로도 다룰 수 있는 텍스트 포맷(ADT 및 ARR)을 유지하고 있는 것도 전체 프로젝트를 꾸려 나가는데 큰 도움이 된다. 

달리기를 하는데 발바닥이 저리다면

요즘은 꼭 이틀에 한번 꼴로 뛰어야겠다고 집착하지는 않는다. 회복에 다소 시간이 걸린다고 느끼기 때문이다. 어제는 6.5km를 아주 편안하게 달렸다. 그저께는 뛰러 나갔다가 눈이 많이 와서 중도에 그만 두었다. 넘어져서 다치면 곤란하기 때문이다. 계단에서 발을 헛디뎌 넘어져서 상완골 근위부의 골절상을 입어 본 사람은 다치는 것이 두렵다!

달린 시간 41분 32초, 총 거리는 6.25km.

어제 달리는 도중 오른쪽 발바닥이 조금씩 저림을 느꼈다. 유난히 한쪽 발바닥의 중간 앞부분에 그런 느낌이 온다. 갑자기 이런 일을 겪은 것은 아니다. 족저근막염의 시작인가? 오늘(토요일)은 너무 피곤하여 잠깐 낮잠을 자는 동안 오른쪽 종아리에 쥐가 나기까지 하였다. 챗GPT는 이렇게 답변하였다.

중족 착지, 잘 때 종아리 쥐, 발바닥 중간 앞쪽 저림(전족부/볼 부분) - 이 조합이면 꽤 전형적인 패턴이 있습니다.

중족 착지는 종아리 가자미근(soleus) 과부하를 유발하여 이 근육이 수축된 상태로 오래 있으면 밤에 풀리면서 경련(쥐)이 난다고 한다. 또한 중족 착지 시 체중이 발 앞쪽으로 집중되어 신경 저림이 일어나기 쉽다고 한다. 

출처: 위키백과. SOLEUS라고 표시된 근육 바깥의 장딴지근은 제거된 그림. 이상하다. 쥐가 나서 지금도 붓고 아픈 곳은 장딴지근 같은데?


스트레칭을 꾸준히 하고 지압볼을 발볼에 대고 굴리는 운동이 필요하다. 달리기 입문 뒤로 무릎 관절에는 문제가 생기지 않았다고 자부하고 있었는데 미처 생각하지 않았던 곳에서 신호를 보내고 있다. 스트레칭의 중요성은 아무리 강조해도 지나치지 않으니 달리기 전후에 신경을 써서 꼭 해야 될 것이다.

2025년 12월 5일 금요일

연구수당 책정의 고단함

흔히들 연구과제 인센티브라고 하는 국가연구과제의 연구수당은 연구과제 신청시 총 인건비의 몇%로 한다고 법령에 의해 정해져 있다. 이것을 연말에 적절히 배분하는 것은 과제책임자의 일이다. 연구수당은 급여와 별도로 주어지는 것으로서 과제 추진 및 성과에 대한 기여도에 따라서 나누게 되어 있다. 일종의 자본주의적 요소를 국가연구과제에 담은 것. 과거에는 과제책임자가 대부분을 독식하는 경우도 있었다고 하지만, 요즘은 그렇게 할 수 없다.

여기서 FAIR란 '균등'은 아니다.

인센티브를 지급할 때 모든 사람이 만족하는 분배 원칙이라는 것이 애초에 존재하지 않는다. 한 일에 비하여 너무 적게 받았다고 생각하는 사람은 여러 경로를 통해 불만을 표출하지만, 생각보다 많이 받았다는 사람은 대개 침묵을 지킨다. 작년에는 나름대로의 원칙을 가지고서 집행을 하였는데, 의외로 불만이 많다고 하여 어쩔 수 없이 작년의 원칙을 그대로 고수하지는 않기로 하였다. '변하지 않는 것은 하나도 없다!'—이것은 나의 변하지 않는 신념이다.

사실 연구수당을 지급하는 것은 과제책임자의 고유 권한이기도 하다. 하지만 어느 정도의 합리성과 투명성은 있어야 한다. 그렇다고 하여 지급표를 완전히 인터넷에 올려 버릴 수는 없다!

여기까지는 분배에 따르는 원칙의 문제였다. 다음으로는 실제로 지급 신청서를 쓰는 일이 남았는데, 이게 정말 고역이다. 과제가 여러 개이고 저마다 참여기간이 다르며, 때로는 중도 입사자와 퇴사자, 그리고 직급이 바뀐 경우도 있어서 고려할 점이 한 두 가지가 아니다. 직원의 도움을 받아서 과제 참여율과 기간 등에 대한 엑셀워크시트를 여러 장 만든 뒤 계속 숫자를 넣어 가며 조절을 해야 한다. 

과거에는 참여자 개별적으로 '얼마'라고 금액을 넣을 수 있었던 것 같은데, 지금은 전부 기여율을 입력해야 된다. 인건비(따라서 참여율과 관련이 있음)에 연동되어 지급되는 인센티브의 한 부분에는 손을 댈 수 없고, 기여율로 결정되는 나머지 부분만을 조정하여 모두가 무난하게 만족하는 수치를 뽑아야 한다. 그러나 기여율의 합은 한 과제에 대해서 항상 100%가 되어야 한다. 

예를 들어 참여자가 4명인 연구과제를 생각해 보자. 기여율 칸에 1:1:1:1 또는 무엇이든 동일한 숫자를 넷 써 넣으면 자동으로 4등분이 되는 시스템이라면 참 좋겠다. 그러나 일일이 25라는 퍼센트 수를 다 넣어야 한다. 따라서 별도의 엑셀표를 만들어서 정확히 백분율로 환산한 숫자를 옮겨 적어야 한다. 그리고 평가 의견(근거)도 채워 넣어야 하고... 약 240개의 row에 대해서 말이다! 물론 내가 일하는 조직의 구성원이 240명이라는 뜻은 아니다. 

차라리 여러 과제의 인센티브를 풀링하여 놓은 뒤 개별적으로 등급을 부여하는 것으로 갈음하면 좋겠다. 하지만 PBS는 우리를 그렇게 편하게 일하도록 놔두지 않는다. 약간 과장하여 말하자면, 내가 지금 쓰는 컴퓨터, 볼펜 한 자루에도 '어느 과제에서 산 것이다'라는 꼬리표를 달아야 하는 식이다. 이런 비유는 어떨까? 나의 오늘 오전 10시부터 11시까지의 업무는 어느 과제를 위한 것인가? 만일 그 한 시간에 대해서 인건비를 지급한다면, 내가 참여하는 여러 과제에서 어떤 비율로 지급한다고 보아야 하나?사실 이런 정신에 입각한 계산을 요구한다.

따라서 서로 넘나들기 어려운 과제 '분리' 칸막이를 놓고 그 안에서 인센티브를 산정해야 하며, 이를 개인별로 합쳤을 때 또 나름대로 합리성이 있어야 한다. 그러나 현실은 이렇게 단순하지 않다. '여기에 이 사람이 참여연구원으로 들어와 있는데 도대체 그 과제와 관련하여 무슨 일을 했는가?'라고 물었을 때 본인이든 관리자든 칼로 물 베듯이 늘 정답을 말하기는 어렵다.

작년에는 연구수당 지급 신청서 작성에 너무 많은 시간을 쏟아부었다. 그래서 올해는 마감일에 임박하여 입력을 하려고 생각했는데—분배 원칙은 이미 수립해 두었으므로—막상 표를 열어 보니 역시 신경을 써야 할 요소가 너무나 많다. 하루 이틀 고민하고 작업해서 될 일이 아니다.

며칠 동안 몇 장의 엑셀표 앞에서 한바탕 '서커스'를 치를 생각을 하니 걱정이 앞선다. 조금이라도 실수가 있으면 안되는데...

2025년 12월 2일 화요일

도스 시절의 향수를 느끼게 하는 드럼 패턴 재생기 Ardule Pattern Studio(aps.py)

Nano Ardule Drum Pattern Player에서 사용할 드럼 패턴 자료 파일(ADP/ADT)을 PC에서 미리 확인하기 위한 프로그램을 만들어 보았다. 이름은 Ardule Pattern Studio(APS). 예전 DOS 시절의 향수를 불러 일으키는 텍스트 기반의 도구이다. 아마 80년대 Norton Commander의 느낌이 날 것이다. 바탕을 파랑색으로 바꾸면 더 비슷해 보이지 않을까? 이미 만들어 둔 adt2adp.py를 재사용하는 조건으로 이 파이썬 스크립트를 만들고 테스트하는 데에는 두 시간 정도 밖에 걸리지 않았다. 놀라운 챗GPT의 힘이여! 핵심 기능을 먼저 구현한 뒤 추가적인 기능을 덧붙여 나가는 과정을 택했기에 오류도 거의 발생하지 않았다.

파이썬으로 짜여진 APS v0.7은 주석을 포함하여 약 1100줄 정도. 눈으로 보고, 소리를 귀로 듣는다! 

드럼 패턴을 화면에 보여주고, MIDI 기능을 이용하여 재생도 할 수 있다. 추가할 기능도 몇 가지 생각해 두었다. 패턴을 조합하여 전체 곡에 해당하는 자료를 만드는 체인 에디터 기능이 다음 목표이다. 체인 에디터란, 이미 준비된 패턴을 조합하여 한 곡 전체에 해당하는 데이터를 만드는 것이다. 저장 방식은 전용 문법을 사용한 텍스트 파일 형태(.ARR), 또는 이를 바이너리로 전환한 것 모두를 고려하고 있다. 다음은 어제 촬영한 v0.7의 작동 영상이다.


아두이노 나노 에브리에서 체인 에디팅을 하는 것도 가능할 것이다. 그러나 몇 개 되지 않는 버튼과 인코더 하나, 그리고 1602 LCD라는 열악한 환경에서 이를 힘겹게 구현하는 것은 쉽지 않을 것이다. 

APS는 PC에서 ADP/ADT를 관리하고 ARR 관련 작업을 하는 도구로서 매우 유용하게 쓰일 것이다. 하나의 목적을 위해 관리하는 프로그램과 문서가 점점 많아지고 있기 때문에 처음에는 전혀 생각하지 않았던 GitHub를 활용하는 것을 심각하게 고려하게 되었다. 나도 일이 이렇게까지 전개될 줄은 몰랐다.

여담이지만 ADP는 원래 Microsoft Access Project 파일의 확장자라고 한다. 그래서 Gmail이나 네이버 메일에서 이를 첨부하는 것을 금지하고 있으며, 심지어 zip 파일의 내부까지도 검사를 하여 차단한다. 이러한 사실을 미리 알았다면 나의 드럼 패턴 정보 파일에 ADP(Ardule Drum Pattern)라는 확장자를 붙이지는 않았을 것이다. 그러나 동일한 정보를 수록한 텍스트 파일(ADT)를 별도로 갖고 있었기 때문에 메일에 첨부할 수 있었다. ADT를 유지하는 것이 과연 필요한지 고민을 하고 있었는데 아직 쓸모는 남아 있는 것 같다.


PC 쪽에서 해야 할 일

인터넷을 뒤져서 드럼 패턴이 수록된 MIDI 파일을 구하는 것으로부터 시작한다. 드럼 패턴 들어있는 파일도 있고, 유명한 8-90년대 팝송을 MIDI 파일로 만들어 놓은 것도 있다. 두번째의 경우라면 드럼 채널에 해당하는 것을 뽑아내는 유틸리티를 파이썬으로 만들 수 있다.

만약 MIDI 파일이 type 1이라면 type 0으로 전환한다. 그 다음, 이를 2-bar 단위로 분할하여 작은 MIDI 파일을 만든다. 이것으로부터 ADT를 먼저 만들고, adt2adp.py를 이용하여 바이너리 형태의 파일인 ADP를 만들게 된다. ADP는 마이크로SD카드에 저장하여 Nano Ardule에 연결한다. 물론 ADP 파일과 더불어 별도의 인덱스 파일을 만들어서 같이 저장해야 한다.

오늘 소개한 aps.py는 ADT 또는 ADP 파일을 PC쪽에서 간편하게 확인하기 위하여 하룻저녁에 뚝딱 개발한 것이다. 일반적인 패턴과 '브레이크' 패턴을 구분하여 패턴 파일명을 변경하기 위한 것이 근본적인 개발 목표였다. 인공지능으로 하여금 자동으로 브레이크를 구별하게 만든다면? 불가능한 일은 아니지만, 처리할 패턴이 수만 개 정도 된다면 모를까, 차라리 눈과 귀에 의존하는 것이 더욱 정확할 것이다.

aps.py의 다음 단계 개발 목표는 ADT/ADP 파일을 엮는 체인 에디팅 기능을 구현하는 것이다. 이 단계에 이르면 Ardule Pattern Studio라는 이름이 비로소 의미를 갖게 될 것이다. 그리드 형태의 패턴 자체를 편집하는 기능까지... 글쎄, 욕심이 과하다. DIY로 드럼 패턴 자체의 에디팅을 하는 도구를 만드느니 차라리 라즈베리 파이에서 Hydrogen을 쓰는 것이 더 낫겠다.

배움의 목적이라는 GitHub 생태계에 들어가는 것이 더 중요하고도 시급하다. 그 다음에는 패턴을 이어서 곡 단위의 파일을 만들도록('체인 에디팅') aps.py를 매만지는 것이고, 패턴 에디팅은 그 다음에 생각하도록 하자.

겨우 하루 매만졌는데 이렇게 발전했다. 패턴 체인 편집 기능(저장 및 불러오기 포함)을 일부 구현하였으며, ARR 파일의 포맷도 형태를 갖추어 나가고 있다.


쿠팡 정보유출 사건과 관련한 집단 손해배상 소송에 참여하다

쿠팡의 정보유출 사태 관련 뉴스가 연일 언론을 가득 채우고 있다. 편리함 이면에는 배송 노동자의 과로사가 있고, 욕망의 껍질(즉 포장재)은 산더미를 이룬다. 나 역시 쿠팡을 편리하게 이용해 온 사람이다. 잠자리에 들기 전, 휴대폰을 꺼내 만지작거리기만 하면 다음날 아침거리가 도착하기 때문이다. 그러나 고객의 이러한 쇼핑 방식 전환에 따라서 정작 지역 시장에서는 좋은 식재료를 찾기가 어렵게 되었다. 이미 조리가 다 되어 있거나 간편하게 먹을 수 있는 밀키트가 진열대를 차지하고 있으니 말이다. 가장 최근의 놀라운 경험은 코트를 수선하기 위한 단추조차 소량으로 쿠팡에서 살 수 있었다는 것. 실정이 이러하니 지역 백화점이나 대형 마트 및 수퍼마켓과 문구점 등을 비롯한 오프라인 시장은 편의점과 다이소만 남긴채 붕괴하고 있다.

편리함이 세상을 너무 이상한 방식으로 변하게 만든다는 우려 때문에 '쿠팡 없이 살아볼까?'라는 생각을 가끔 하고 있었다. 그러던 중, 쿠팡 탈퇴라는 결심을 하게 만든 사건이 발생했으니 바로 최근의 정보유출 사건이다. 생각보다 매우 심각한 사건인데, 당사자인 쿠팡은 너무나 태연하고 미온적인 태도를 보였다. 이번 사태는 김범석 전 의장의 과거 언행을 다시 찾아보게 만들었다. 

2019년 12월 쿠팡 고위관계자의 발언을 보도한 뉴스에 따르면 김범석 전 의장은 "한국인은 큰 물에서 놀지 못해 시야가 좁고, 스마트하지 못하며 정직하지 않은 민족"이라고 말했다는 것입니다. 관련 보도 기사는 모두 삭제돼 버렸습니다. (뉴스웰 링크)

물류센터 화재나 노동자 과로사 문제 등이 불거질 때, 책임을 지려 한다기보다 국내의 법적 책임을 회피하려는 듯한 모습을 보여 왔기 때문이다. 그래서 나는 쿠팡 회원 탈퇴는 물론, 김경호 변호사를 통한 단체소송에도 참가하였다.

쿠팡 정보유출에 시민들 “소송 참여합니다”···수천명 ‘본격 소송전’ 나서 (경향신문 링크)

이번 사태에 대한 별도의 자동생성 글을 위키 사이트에 등록하였다.

쿠팡 고객정보 유출 사태는 선택적, 한시적 분노로 끝날 것인가

이번 사태가 일시적인 분노로 끝날 것이 아니라 세상을 더욱 안전하고 편리하게 바꾸는 데 기여하기를 바랄 뿐이다.

2025년 11월 30일 일요일

무선이냐 유선이냐

같이 활동하는 밴드 동아리의 기타리스트 회원들이 무선 송수신기를 들고 나타나기 시작하였다. 베이스를 치는 나는 DI 박스 외에는 별다른 악세사리도 갖고 있지 못하며, 거추장스럽게 악기용 긴 케이블을 늘 챙겨 다니는 터였다. 설상가상으로 직접 커넥터를 납땜하여 만든 케이블이 갑작스럽게 단선이 되었다. 2023년의 작업 기록은 여기('기타 케이블의 1/4" 플러그 교체')에 있다. 납땜에도 등급이 있다. 숙련공이 좋은 자재를 써서 납땜한 케이블, 아마추어가 대충 납땜하여 작은 충격에도 납땜이 끊어지는 케이블.

"그래서 다들 무선 시스템을 쓰는 모양이네..."

무선 시스템의 편리함은 굳이 강조할 필요가 없다. 우리 밴드에서도 무선 마이크를 구입한 이후로 다시는 유선 마이크를 쓰지 않게 되었으니 말이다. 단점이 있다면 나 정도의 아마추어가 납땜인두를 들고 유지보수를 하기는 곤란하다는 것.

아두이노 공작(특히 코딩)에 몇 개월 동안이나 몰두하느라 한동안 납땜을 잊고 살았었다. 충격에도 잘 견디도록 내 나름대로는 견고하게 다시 납땜을 하였다. 심선의 가닥이 너무 적어도 납땜이 튼튼하게 유지되기 곤란하다. 좋은 자재와 실력으로부터 좋은 물건이 나옴을 명심하자.

보수작업을 마친 후. 이 케이블은 아마도 1987년 근처에 처음으로 전기 기타를 구입하면서 같이 샀던 것으로 기억한다. 케이블 자체의 품질은 별로 좋지 않다. 심선이 매우 가늘고 그 수도 적다.

Nano Ardule 'Drum Pattern Player'는 여전히 진화를 거치고 있다. 오늘은 남아 있던 버그를 잡느라 많은 시간을 보냈다. 비상용 패턴을 코드에 심는 일은 아직 제대로 시작하지 못하였다.

커스텀 캐릭터를 만들어서 재생 상태를 나타내는 삼각형을 표현해 보았다. 검색을 해 보니 LCD Custom Character Generator라는 웹사이트도 있었다.

LCD UI는 아직도 최적화를 향해 갈 길이 멀다. 표시할 공간은 부족하고, 보여주고 싶은 정보는 많고... 만약 한 장르의 패턴이 100개, 즉 세 자리를 넘어가면 어떻게 할 것인가? 'BPM'을 더 짧게 줄일 것인가?

사실 고른 베이스 소리를 내기 위해서 장비를 하나쯤은 들여야 한다고 생각하고 있었다. 연습이 모든 부족함을 다 커버하기는 곤란하다는 것이 나의 변명이다. 베이스 세계에서 컴프레서는 결코 싸지 않은 물건이다. 적당한 중국산 제품을 물색한 끝에 Sonicake B Factory Bass Preamp Overdrive Pedal with EQ(웹사이트)를 구입하였다. 알리익스프레스에서 주문을 했기에 국내에 유통되는 가격보다는 싸게 구입하였다. 물건을 받은 후에는 9볼트 전지를 써서 DC 어댑터용 플러그를 연결할 수 있는 간단한 장치도 만들었다. 서너 시간 정도의 공연에는 버틸 수 있을 것이다. 오래 쓰고 싶다면 1.5V 알칼라인 건전지를 6개 직렬로 연결하여 9V를 만드는 배터리 홀더를 쓰면 된다.


질문: 오른쪽 버튼(파워)를 누르지 않은 상태에서 왼쪽 버튼(오버드라이브)를 눌러도 LED가 들어온다. 작동이 되는 것일까? 그래서는 안 될 터인데?

당초 계획보다 늦어졌지만 12월 중에는 Nano Ardule 최종판의 소개 동영상을 찍을 수 있을 것 같다.

다시 찾은 제주도

신혼여행 당시에 제주도에 와서 들렀던 관광지가 어렴풋하게 기억이 난다. 여미지식물원, 성읍민속마을, 산굼부리... 그로부터 30년이 넘게 지나 다시 아내와 함께 제주도를 찾게 되었다. 최근 수 년 동안 제주도를 몇 번 왔었지만 학회 출장으로 오느라 주변을 여유 있게 둘러볼 기회가 없었다. 

이번에는 아내와 함께 여미지를 꼭 다시 가 보고 싶었다. 지금은 제주도에서 그렇게 인기가 많은 곳은 아니라고 한다. 과거보다 시설이 낡기는 했지만, 아직까지도 명맥을 유지하고 있어서 무척 반가웠다. 꽃과 나무를 가꾸는 사람들의 정성과 마음가짐을 정말 존경한다. 전날 방문했던 카멜리아 힐(동백 수목원)도 정말 좋았다. 

여미지의 상징과 같은 유리온실. 바깥에 조성된 정원이 이보다 열 배는 크다.

온실 전망대에서 바라본 한라산.


일본 정원. 펜탁스 Q10으로 요란하게 색을 입혀 보았다.

프랑스 정원. 바로 뒤에는 이태리 정원이 있다.


산방산 앞 용머리 해안가도 절경이었다. 평생 다른 곳을 가지 못하고 바닷가 바위에 붙어 사는 따개비와 거북손을 보니 측은한 생각이 들었다. 구하기 어려운 별미라고 그걸 보자마자 따서 먹는 사람도 있었다. 평온히 자연과 더불어 살아가다가 별안간 사람의 입 속으로 들어가는 거북손은 무슨 죄란 말인가.

산방산.






예상보다 좋은 느낌으로 다가온 곳은 무릉곶자왈이었다. 이번의 제주도 방문이 있기 전에는 곶자왈이 무엇인지도 몰랐으니까. 촬영한 영상은 유튜브 쇼츠로 남겼다. 제주도에는 온갖 박물관과 맛집도 많이 있지만, 그런 문명과 자본주의의 흔적보다는 제주 특유의 자연환경과 문화를 감상할 수 있는 곳을 들르는 것에 나는 더 큰 의미를 두고 있다. 아내 도한 나와 비슷한 생각의 소유자이다.


덤으로 독특한 모양의 방주교회까지.


제주국제공항을 떠나기 직전의 마지막 방문지는 새별오름이었다. 석양을 바라볼 수 있는 서쪽 코스로 접근했던 것이 큰 행운이었다. 아부오름과 같이 분화구가 있지는 않으나. 억새의 물결이 찬란하게 빛나고 있었다.




이번 여행도 사전에 철저하게 조사를 하고 떠나지는 못하였다. 청주국제공항에서 주차대행을 이용한 정도가 새로운 점일 것이다. 떠날 때 3번 게이트에서 차량을 맡긴 뒤 돌아오면 바로 앞의 주차빌딩에서 차를 찾아 나오는 시스템이다. 열쇠는 스마트 보관함에서 찾으면 되므로 밤 늦게 돌아와도 큰 문제가 없다. 이 서비스를 제공하는 아마노코리아는 주차와 렌터카 등의 분야에서 영업활동을 하고 있다고 한다.

이번 여행을 위해 트립닷컴에서 이스타항공의 항공권을 예약하였는데, 돌아오는 비행기를 모바일 체크인하지 못하여 카운터에서 좌석을 배정받아야 했다. 왜냐하면 인터넷으로는 좌석이 전부 찬 것처럼 나왔기 때문이다. 혹시 오버부킹은 아닌지 걱정이 되어 전화로 문의하였더니 걱정할 필요가 없다고 하였다. 최소한 국내 항공사는 오버부킹을 하지는 않는다는 정보가 많이 있었다. 확실한 것은 아니지만, 인터넷으로 사전에 지정할 수 있는 좌석이 제한되어 있다던가.

몇 가지 사진을 추가로 소개해 본다.


'이태리정원은 15세기 이태리에서 발달한 노단건축식 정원을 대표하는 빌라데스테(Villa d'este)의 오바토(ovato) 분수를 재현한 정원이다.' - 여미지에서.

무릉곶자왈에서.


카멜리아 힐에서 바라본 산방산.

뒤를 돌아보면 한라산이 보인다.

본태미술관.

방주교회.