diff --git a/PROJECT_MEMORY.md b/PROJECT_MEMORY.md
index cf0702d..f22ef35 100644
--- a/PROJECT_MEMORY.md
+++ b/PROJECT_MEMORY.md
@@ -25,6 +25,15 @@
- 개발 환경에서 MIDI 파서 검사와 백엔드 단위 테스트 5개를 통과했다. 운영 HTTPS 첫 화면과 비회원 공개 목록 및 MIDI 파일 5개 다운로드를 확인했다. 기존 회원 업로드 2개를 보존했고 백엔드만 갱신했다. 프런트엔드는 재시작하지 않았다.
- 실제 USB MIDI 장치로 다섯 곡의 손별 따라치기를 확인하는 작업은 남아 있다.
+## 2026-10-09 연습곡 30개 교체
+
+- 위 다섯 곡은 선율이 지나치게 비슷하다는 의견을 반영해 공개 목록에서 제거했다. 해당 기본 자료의 파일과 생성 스크립트도 교체했다. 회원 업로드에는 손대지 않았다.
+- Mutopia Project에 원본과 권리 정보가 함께 있는 서로 다른 피아노곡 30개를 골랐다. 부르크뮐러 Op. 100에서 18곡과 슈만 Op. 68에서 12곡이다. 도입부에는 각 원곡의 첫 선율을 천천히 제시하고 마무리에는 마지막 선율과 긴 종지음을 넣었다. 각 편곡은 69~134초다.
+- 오른손의 중심 선율과 왼손 저음을 양손 연습용으로 정리했다. 오른손 음표 밀도에 따라 입문과 쉬움과 중급으로 분류했다. 원본 MIDI와 RDF 및 곡별 출처와 라이선스를 `backend/tools/practice_sources`와 `backend/sample_midi/practice_collection.json`에 보관한다. 슈만 자료의 CC BY-SA 버전은 편곡본에도 적용한다.
+- `node scripts/build_practice_collection.mjs`로 파일을 다시 만들고 `node scripts/check_practice_collection.mjs`로 30개의 서로 다른 선율과 양손 트랙 및 도입부와 마무리와 길이를 검사한다. 서버의 격리 폴더에서 백엔드 단위 테스트 5개를 통과했다.
+- 운영 DB와 수정 대상 소스 및 기존 MIDI 파일의 백업은 `/home/syskiller/piano/backups/bach-to-basics/20261009-practice-30-intro-ending`에 있다. 백엔드만 다시 빌드했고 프런트엔드는 재시작하지 않았다. 공개 HTTPS 첫 화면과 비회원 목록의 새 곡 30개 및 각 파일의 MIDI 다운로드를 확인했다. 기존 회원 업로드 2건은 유지됐다.
+- 30곡은 공개 도메인 또는 CC BY-SA로 표시된 고전 연습곡의 편곡이다. 특정 한국 쇼츠의 곡을 전사한 자료는 아니다. 실제 피아노 건반으로 각 곡의 연주감과 MIDI 입력을 확인하는 작업은 남아 있다.
+
## 2026-10-03 현재 상태
- Docker의 `piano-bach-frontend`와 `piano-bach-backend`로 운영한다.
diff --git a/README.md b/README.md
index 4c49e1d..065255c 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ A browser-based piano practice tool. Drop in a **MIDI file**, a **MusicXML score
이 저장소는 원본 Bach to Basics를 바탕으로 `learn.itosm.co.kr`에서 운영하는 한국어판이다. 독립 회원과 곡·악보 보관함을 제공한다. 비회원은 공개 곡을 연주할 수 있으며 회원은 MIDI와 MusicXML과 MXL 및 PDF 악보를 올려 공개 범위와 회원 등급을 지정할 수 있다.
-기본 공개 자료는 Mutopia Project의 공개 도메인 MIDI와 그 원곡에서 만든 단계별 연습 편곡 및 자체 제작한 약 1분 길이의 초보자용 양손 MIDI다. `엘리제를 위하여 WoO 59`와 `별빛 계단`은 난이도가 다른 버전을 같은 제목 아래에서 선택한다. 출처와 편곡 범위 및 연주 기법 참고 자료는 [기본 공개 샘플 출처](docs/THIRD_PARTY_SAMPLES.md)에 기록했다. 회원과 운영 구성은 [회원과 곡 보관함 운영 문서](docs/LIBRARY-AUTH-KO.md)를 따른다.
+기본 공개 자료는 Mutopia Project의 MIDI와 그 원곡에서 만든 단계별 연습 편곡 및 도입부와 마무리를 갖춘 양손 연습곡 30개다. `엘리제를 위하여 WoO 59`는 난이도가 다른 버전을 같은 제목 아래에서 선택한다. 출처와 편곡 범위 및 권리 표시는 [기본 공개 샘플 출처](docs/THIRD_PARTY_SAMPLES.md)에 기록했다. 회원과 운영 구성은 [회원과 곡 보관함 운영 문서](docs/LIBRARY-AUTH-KO.md)를 따른다.
[](https://raw.githubusercontent.com/gigliof/bach-to-basics/main/docs/demo.gif)
diff --git a/backend/library_db.py b/backend/library_db.py
index 4fa95f4..e977be0 100644
--- a/backend/library_db.py
+++ b/backend/library_db.py
@@ -108,56 +108,6 @@ def delete_session(token: str | None) -> None:
SAMPLES = (
- {
- "storage_name": "starlight-steps-beginner.mid",
- "title": "별빛 계단",
- "artist": "ITOSM 피아노 학습실",
- "description": "약 76초 분량의 직접 만든 입문곡입니다. 오른손은 간단한 선율을 치고 왼손은 근음과 5도를 번갈아 누릅니다. 분산 화음 기법 참고: Bill Hilton의 유튜브 강좌(8AMZph76kqM).",
- "difficulty": "beginner",
- "genre": "연습곡",
- "arrangement": "piano_solo",
- "tags": ["양손", "입문", "분산 화음", "근음", "5도", "자체 제작"],
- },
- {
- "storage_name": "starlight-steps-easy.mid",
- "title": "별빛 계단",
- "artist": "ITOSM 피아노 학습실",
- "description": "같은 선율의 약 69초 분량 쉬운 버전입니다. 왼손 네 음을 차례로 펼쳐 소리를 풍성하게 만듭니다. 분산 화음 기법 참고: Bill Hilton의 유튜브 강좌(8AMZph76kqM).",
- "difficulty": "easy",
- "genre": "연습곡",
- "arrangement": "piano_solo",
- "tags": ["양손", "쉬움", "분산 화음", "단계별 연습", "자체 제작"],
- },
- {
- "storage_name": "evening-waltz-beginner.mid",
- "title": "저녁빛 왈츠",
- "artist": "ITOSM 피아노 학습실",
- "description": "약 64초 분량의 직접 만든 왈츠입니다. 왼손은 낮은 음 하나와 같은 음 두 번을 반복하고 오른손은 한 박에 한 음씩 연주합니다.",
- "difficulty": "beginner",
- "genre": "왈츠",
- "arrangement": "piano_solo",
- "tags": ["양손", "입문", "3박자", "왈츠", "자체 제작"],
- },
- {
- "storage_name": "sunrise-arpeggio-easy.mid",
- "title": "해돋이 아르페지오",
- "artist": "ITOSM 피아노 학습실",
- "description": "약 67초 분량의 직접 만든 연습곡입니다. 오른손 네 음을 차례로 누르는 동안 왼손은 낮은 근음 하나만 유지합니다. 아르페지오 기법 참고: Bill Hilton의 유튜브 강좌(8AMZph76kqM).",
- "difficulty": "easy",
- "genre": "연습곡",
- "arrangement": "piano_solo",
- "tags": ["양손", "쉬움", "아르페지오", "근음", "자체 제작"],
- },
- {
- "storage_name": "black-key-pentatonic-beginner.mid",
- "title": "검은 건반 산책",
- "artist": "ITOSM 피아노 학습실",
- "description": "약 74초 분량의 직접 만든 입문곡입니다. 검은 건반 다섯 음만으로 선율을 만들고 왼손은 두 음을 번갈아 누릅니다.",
- "difficulty": "beginner",
- "genre": "연습곡",
- "arrangement": "piano_solo",
- "tags": ["양손", "입문", "검은 건반", "5음 음계", "자체 제작"],
- },
{
"storage_name": "minuet-bwv-anh-114-beginner.mid",
"title": "미뉴에트 사장조 BWV Anh. 114",
@@ -230,11 +180,27 @@ SAMPLES = (
},
)
+# 별도 목록은 원본 MIDI와 권리 표시를 함께 보관한다. 화면에 쓰는 필드만 기본 샘플에 합친다.
+PRACTICE_COLLECTION = json.loads(
+ (Path(__file__).with_name("sample_midi") / "practice_collection.json").read_text(encoding="utf-8")
+)
+SAMPLES += tuple(
+ {key: item[key] for key in (
+ "storage_name", "title", "artist", "description", "difficulty", "genre", "arrangement", "tags"
+ )}
+ for item in PRACTICE_COLLECTION
+)
+
LEGACY_SAMPLE_KEYS = (
"first-snow-melody.mid",
"first-snow-accompaniment.mid",
"blue-breeze-walk.mid",
"little-morning-waltz.mid",
+ "starlight-steps-beginner.mid",
+ "starlight-steps-easy.mid",
+ "evening-waltz-beginner.mid",
+ "sunrise-arpeggio-easy.mid",
+ "black-key-pentatonic-beginner.mid",
)
diff --git a/backend/sample_midi/black-key-pentatonic-beginner.mid b/backend/sample_midi/black-key-pentatonic-beginner.mid
deleted file mode 100644
index c31ddf0..0000000
Binary files a/backend/sample_midi/black-key-pentatonic-beginner.mid and /dev/null differ
diff --git a/backend/sample_midi/evening-waltz-beginner.mid b/backend/sample_midi/evening-waltz-beginner.mid
deleted file mode 100644
index 2ee38f2..0000000
Binary files a/backend/sample_midi/evening-waltz-beginner.mid and /dev/null differ
diff --git a/backend/sample_midi/practice-burgmuller-01.mid b/backend/sample_midi/practice-burgmuller-01.mid
new file mode 100644
index 0000000..09b4f78
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-01.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-02.mid b/backend/sample_midi/practice-burgmuller-02.mid
new file mode 100644
index 0000000..202a424
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-02.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-03.mid b/backend/sample_midi/practice-burgmuller-03.mid
new file mode 100644
index 0000000..2ee05b3
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-03.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-04.mid b/backend/sample_midi/practice-burgmuller-04.mid
new file mode 100644
index 0000000..596652a
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-04.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-05.mid b/backend/sample_midi/practice-burgmuller-05.mid
new file mode 100644
index 0000000..65f7153
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-05.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-06.mid b/backend/sample_midi/practice-burgmuller-06.mid
new file mode 100644
index 0000000..eebf30c
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-06.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-07.mid b/backend/sample_midi/practice-burgmuller-07.mid
new file mode 100644
index 0000000..74cabfb
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-07.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-08.mid b/backend/sample_midi/practice-burgmuller-08.mid
new file mode 100644
index 0000000..bb4517a
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-08.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-09.mid b/backend/sample_midi/practice-burgmuller-09.mid
new file mode 100644
index 0000000..6dece0f
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-09.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-10.mid b/backend/sample_midi/practice-burgmuller-10.mid
new file mode 100644
index 0000000..4bb2cfd
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-10.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-11.mid b/backend/sample_midi/practice-burgmuller-11.mid
new file mode 100644
index 0000000..74c3a31
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-11.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-12.mid b/backend/sample_midi/practice-burgmuller-12.mid
new file mode 100644
index 0000000..2eea128
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-12.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-13.mid b/backend/sample_midi/practice-burgmuller-13.mid
new file mode 100644
index 0000000..f7e2215
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-13.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-14.mid b/backend/sample_midi/practice-burgmuller-14.mid
new file mode 100644
index 0000000..54cfc26
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-14.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-15.mid b/backend/sample_midi/practice-burgmuller-15.mid
new file mode 100644
index 0000000..83948e4
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-15.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-16.mid b/backend/sample_midi/practice-burgmuller-16.mid
new file mode 100644
index 0000000..a7c593e
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-16.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-17.mid b/backend/sample_midi/practice-burgmuller-17.mid
new file mode 100644
index 0000000..3f24524
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-17.mid differ
diff --git a/backend/sample_midi/practice-burgmuller-18.mid b/backend/sample_midi/practice-burgmuller-18.mid
new file mode 100644
index 0000000..38cc258
Binary files /dev/null and b/backend/sample_midi/practice-burgmuller-18.mid differ
diff --git a/backend/sample_midi/practice-schumann-01.mid b/backend/sample_midi/practice-schumann-01.mid
new file mode 100644
index 0000000..2a9fdfa
Binary files /dev/null and b/backend/sample_midi/practice-schumann-01.mid differ
diff --git a/backend/sample_midi/practice-schumann-02.mid b/backend/sample_midi/practice-schumann-02.mid
new file mode 100644
index 0000000..78deece
Binary files /dev/null and b/backend/sample_midi/practice-schumann-02.mid differ
diff --git a/backend/sample_midi/practice-schumann-03.mid b/backend/sample_midi/practice-schumann-03.mid
new file mode 100644
index 0000000..3601646
Binary files /dev/null and b/backend/sample_midi/practice-schumann-03.mid differ
diff --git a/backend/sample_midi/practice-schumann-04.mid b/backend/sample_midi/practice-schumann-04.mid
new file mode 100644
index 0000000..6e77758
Binary files /dev/null and b/backend/sample_midi/practice-schumann-04.mid differ
diff --git a/backend/sample_midi/practice-schumann-05.mid b/backend/sample_midi/practice-schumann-05.mid
new file mode 100644
index 0000000..e11161c
Binary files /dev/null and b/backend/sample_midi/practice-schumann-05.mid differ
diff --git a/backend/sample_midi/practice-schumann-06.mid b/backend/sample_midi/practice-schumann-06.mid
new file mode 100644
index 0000000..f5f7fdf
Binary files /dev/null and b/backend/sample_midi/practice-schumann-06.mid differ
diff --git a/backend/sample_midi/practice-schumann-07.mid b/backend/sample_midi/practice-schumann-07.mid
new file mode 100644
index 0000000..e076537
Binary files /dev/null and b/backend/sample_midi/practice-schumann-07.mid differ
diff --git a/backend/sample_midi/practice-schumann-08.mid b/backend/sample_midi/practice-schumann-08.mid
new file mode 100644
index 0000000..9077f97
Binary files /dev/null and b/backend/sample_midi/practice-schumann-08.mid differ
diff --git a/backend/sample_midi/practice-schumann-09.mid b/backend/sample_midi/practice-schumann-09.mid
new file mode 100644
index 0000000..f7ccf48
Binary files /dev/null and b/backend/sample_midi/practice-schumann-09.mid differ
diff --git a/backend/sample_midi/practice-schumann-10.mid b/backend/sample_midi/practice-schumann-10.mid
new file mode 100644
index 0000000..395741e
Binary files /dev/null and b/backend/sample_midi/practice-schumann-10.mid differ
diff --git a/backend/sample_midi/practice-schumann-11.mid b/backend/sample_midi/practice-schumann-11.mid
new file mode 100644
index 0000000..8ddc345
Binary files /dev/null and b/backend/sample_midi/practice-schumann-11.mid differ
diff --git a/backend/sample_midi/practice-schumann-12.mid b/backend/sample_midi/practice-schumann-12.mid
new file mode 100644
index 0000000..150201f
Binary files /dev/null and b/backend/sample_midi/practice-schumann-12.mid differ
diff --git a/backend/sample_midi/practice_collection.json b/backend/sample_midi/practice_collection.json
new file mode 100644
index 0000000..2a21d9e
--- /dev/null
+++ b/backend/sample_midi/practice_collection.json
@@ -0,0 +1,722 @@
+[
+ {
+ "storage_name": "practice-burgmuller-01.mid",
+ "title": "부르크뮐러 25곡 1번 순진",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "La Candeur 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-202. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "easy",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "쉬움",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-01/25EF-01.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-01/25EF-01.rdf",
+ "source_title": "La Candeur",
+ "source_id": "Mutopia-2013/01/12-202",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-02.mid",
+ "title": "부르크뮐러 25곡 2번 아라베스크",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "l'Arabesque 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-203. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "입문",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-02/25EF-02.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-02/25EF-02.rdf",
+ "source_title": "l'Arabesque",
+ "source_id": "Mutopia-2013/01/12-203",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-03.mid",
+ "title": "부르크뮐러 25곡 3번 목가",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "La Pastorale 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-218. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "입문",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-03/25EF-03.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-03/25EF-03.rdf",
+ "source_title": "La Pastorale",
+ "source_id": "Mutopia-2013/01/12-218",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-04.mid",
+ "title": "부르크뮐러 25곡 4번 작은 모임",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "La Petite Réunion 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-219. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "easy",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "쉬움",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-04/25EF-04.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-04/25EF-04.rdf",
+ "source_title": "La Petite Réunion",
+ "source_id": "Mutopia-2013/01/12-219",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-05.mid",
+ "title": "부르크뮐러 25곡 5번 순수",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "Innocence 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-214. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "입문",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-05/25EF-05.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-05/25EF-05.rdf",
+ "source_title": "Innocence",
+ "source_id": "Mutopia-2013/01/12-214",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-06.mid",
+ "title": "부르크뮐러 25곡 6번 진보",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "Progrès 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-215. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "입문",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-06/25EF-06.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-06/25EF-06.rdf",
+ "source_title": "Progrès",
+ "source_id": "Mutopia-2013/01/12-215",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-07.mid",
+ "title": "부르크뮐러 25곡 7번 맑은 시냇물",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "Le Courant Limpide 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-216. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "easy",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "쉬움",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-07/25EF-07.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-07/25EF-07.rdf",
+ "source_title": "Le Courant Limpide",
+ "source_id": "Mutopia-2013/01/12-216",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-08.mid",
+ "title": "부르크뮐러 25곡 8번 우아함",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "La Gracieuse 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-217. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "입문",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-08/25EF-08.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-08/25EF-08.rdf",
+ "source_title": "La Gracieuse",
+ "source_id": "Mutopia-2013/01/12-217",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-09.mid",
+ "title": "부르크뮐러 25곡 9번 사냥",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "La Chasse 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-220. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "easy",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "쉬움",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-09/25EF-09.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-09/25EF-09.rdf",
+ "source_title": "La Chasse",
+ "source_id": "Mutopia-2013/01/12-220",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-10.mid",
+ "title": "부르크뮐러 25곡 10번 여린 꽃",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "Tendre Fleur 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-221. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "easy",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "쉬움",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-10/25EF-10.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-10/25EF-10.rdf",
+ "source_title": "Tendre Fleur",
+ "source_id": "Mutopia-2013/01/12-221",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-11.mid",
+ "title": "부르크뮐러 25곡 11번 할미새",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "La Bergeronnette 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-222. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "입문",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-11/25EF-11.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-11/25EF-11.rdf",
+ "source_title": "La Bergeronnette",
+ "source_id": "Mutopia-2013/01/12-222",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-12.mid",
+ "title": "부르크뮐러 25곡 12번 이별",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "L'Adieu 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-223. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "intermediate",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "중급",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-12/25EF-12.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-12/25EF-12.rdf",
+ "source_title": "L'Adieu",
+ "source_id": "Mutopia-2013/01/12-223",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-13.mid",
+ "title": "부르크뮐러 25곡 13번 위안",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "Consolation 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-224. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "easy",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "쉬움",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-13/25EF-13.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-13/25EF-13.rdf",
+ "source_title": "Consolation",
+ "source_id": "Mutopia-2013/01/12-224",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-14.mid",
+ "title": "부르크뮐러 25곡 14번 슈타이어 춤",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "La Styrienne 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-225. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "easy",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "쉬움",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-14/25EF-14.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-14/25EF-14.rdf",
+ "source_title": "La Styrienne",
+ "source_id": "Mutopia-2013/01/12-225",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-15.mid",
+ "title": "부르크뮐러 25곡 15번 발라드",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "Ballade 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-227. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "easy",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "쉬움",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-15/25EF-15.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-15/25EF-15.rdf",
+ "source_title": "Ballade",
+ "source_id": "Mutopia-2013/01/12-227",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 93.8
+ },
+ {
+ "storage_name": "practice-burgmuller-16.mid",
+ "title": "부르크뮐러 25곡 16번 작은 탄식",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "Douce Plainte 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-228. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "입문",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-16/25EF-16.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-16/25EF-16.rdf",
+ "source_title": "Douce Plainte",
+ "source_id": "Mutopia-2013/01/12-228",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-17.mid",
+ "title": "부르크뮐러 25곡 17번 수다쟁이",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "La Babillarde 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-229. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "입문",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-17/25EF-17.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-17/25EF-17.rdf",
+ "source_title": "La Babillarde",
+ "source_id": "Mutopia-2013/01/12-229",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-burgmuller-18.mid",
+ "title": "부르크뮐러 25곡 18번 불안",
+ "artist": "요한 프리드리히 부르크뮐러",
+ "description": "Inquiétude 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2013/01/12-230. 조판: Bas Wassink. Public Domain.",
+ "difficulty": "easy",
+ "genre": "낭만주의 연습곡",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "부르크뮐러",
+ "쉬움",
+ "공개 도메인"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-18/25EF-18.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-18/25EF-18.rdf",
+ "source_title": "Inquiétude",
+ "source_id": "Mutopia-2013/01/12-230",
+ "maintainer": "Bas Wassink",
+ "license": "Public Domain",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-schumann-01.mid",
+ "title": "슈만 어린이를 위한 앨범 1번 멜로디",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 1.Mélodie 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-647. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "입문",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-01-melodie/schumann-op68-01-melodie.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-01-melodie/schumann-op68-01-melodie.rdf",
+ "source_title": "Album pour la jeunesse - 1.Mélodie",
+ "source_id": "Mutopia-2007/02/11-647",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 107
+ },
+ {
+ "storage_name": "practice-schumann-02.mid",
+ "title": "슈만 어린이를 위한 앨범 2번 군대 행진곡",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 2.Marche militaire 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-650. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "입문",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-02-marche-militaire/schumann-op68-02-marche-militaire.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-02-marche-militaire/schumann-op68-02-marche-militaire.rdf",
+ "source_title": "Album pour la jeunesse - 2.Marche militaire",
+ "source_id": "Mutopia-2007/02/11-650",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-schumann-03.mid",
+ "title": "슈만 어린이를 위한 앨범 3번 흥얼거리는 노래",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 3.Chanson fredonnée 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2011/09/18-651. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 3.0 동일 조건 편곡.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "입문",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-03-chanson-fredonnee/schumann-op68-03-chanson-fredonnee.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-03-chanson-fredonnee/schumann-op68-03-chanson-fredonnee.rdf",
+ "source_title": "Album pour la jeunesse - 3.Chanson fredonnée",
+ "source_id": "Mutopia-2011/09/18-651",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 3.0",
+ "duration_seconds": 106
+ },
+ {
+ "storage_name": "practice-schumann-04.mid",
+ "title": "슈만 어린이를 위한 앨범 4번 코랄",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 4.Un choral 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-782. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "입문",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-04-choral/schumann-op68-04-choral.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-04-choral/schumann-op68-04-choral.rdf",
+ "source_title": "Album pour la jeunesse - 4.Un choral",
+ "source_id": "Mutopia-2007/02/11-782",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 104.7
+ },
+ {
+ "storage_name": "practice-schumann-05.mid",
+ "title": "슈만 어린이를 위한 앨범 5번 작은 소품",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 5.Petite pièce 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-653. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "입문",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-05-petite-piece/schumann-op68-05-petite-piece.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-05-petite-piece/schumann-op68-05-petite-piece.rdf",
+ "source_title": "Album pour la jeunesse - 5.Petite pièce",
+ "source_id": "Mutopia-2007/02/11-653",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 96.6
+ },
+ {
+ "storage_name": "practice-schumann-06.mid",
+ "title": "슈만 어린이를 위한 앨범 6번 가엾은 고아",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 6.Pauvre orpheline 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-687. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "입문",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-06-pauvre-orpheline/schumann-op68-06-pauvre-orpheline.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-06-pauvre-orpheline/schumann-op68-06-pauvre-orpheline.rdf",
+ "source_title": "Album pour la jeunesse - 6.Pauvre orpheline",
+ "source_id": "Mutopia-2007/02/11-687",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 109.8
+ },
+ {
+ "storage_name": "practice-schumann-07.mid",
+ "title": "슈만 어린이를 위한 앨범 7번 사냥꾼의 노래",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 7.Chanson du chasseur 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/05/20-691. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 3.0 동일 조건 편곡.",
+ "difficulty": "easy",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "쉬움",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-07-chanson-du-chasseur/schumann-op68-07-chanson-du-chasseur.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-07-chanson-du-chasseur/schumann-op68-07-chanson-du-chasseur.rdf",
+ "source_title": "Album pour la jeunesse - 7.Chanson du chasseur",
+ "source_id": "Mutopia-2007/05/20-691",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 3.0",
+ "duration_seconds": 82.8
+ },
+ {
+ "storage_name": "practice-schumann-08.mid",
+ "title": "슈만 어린이를 위한 앨범 8번 거친 기수",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 8.Le cavalier sauvage 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-655. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "easy",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "쉬움",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-08-cavalier-sauvage/schumann-op68-08-cavalier-sauvage.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-08-cavalier-sauvage/schumann-op68-08-cavalier-sauvage.rdf",
+ "source_title": "Album pour la jeunesse - 8.Le cavalier sauvage",
+ "source_id": "Mutopia-2007/02/11-655",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-schumann-09.mid",
+ "title": "슈만 어린이를 위한 앨범 9번 민요",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 9.Chanson populaire 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-675. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "입문",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-09-chanson-populaire/schumann-op68-09-chanson-populaire.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-09-chanson-populaire/schumann-op68-09-chanson-populaire.rdf",
+ "source_title": "Album pour la jeunesse - 9.Chanson populaire",
+ "source_id": "Mutopia-2007/02/11-675",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 93.1
+ },
+ {
+ "storage_name": "practice-schumann-10.mid",
+ "title": "슈만 어린이를 위한 앨범 10번 즐거운 농부",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 10.Le gai laboureur 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-659. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "beginner",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "입문",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-10-gai-laboureur/schumann-op68-10-gai-laboureur.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-10-gai-laboureur/schumann-op68-10-gai-laboureur.rdf",
+ "source_title": "Album pour la jeunesse - 10.Le gai laboureur",
+ "source_id": "Mutopia-2007/02/11-659",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 69
+ },
+ {
+ "storage_name": "practice-schumann-11.mid",
+ "title": "슈만 어린이를 위한 앨범 11번 시칠리아 춤곡",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 11.Sicilienne 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-783. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "easy",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "쉬움",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-11-sicilienne/schumann-op68-11-sicilienne.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-11-sicilienne/schumann-op68-11-sicilienne.rdf",
+ "source_title": "Album pour la jeunesse - 11.Sicilienne",
+ "source_id": "Mutopia-2007/02/11-783",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 133.6
+ },
+ {
+ "storage_name": "practice-schumann-12.mid",
+ "title": "슈만 어린이를 위한 앨범 12번 무서운 아저씨",
+ "artist": "로베르트 슈만",
+ "description": "Album pour la jeunesse - 12.Le Père Fouettard 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project Mutopia-2007/02/11-784. 조판: Philippe Hézaine. Creative Commons Attribution-ShareAlike 2.5 동일 조건 편곡.",
+ "difficulty": "intermediate",
+ "genre": "낭만주의 소품",
+ "arrangement": "piano_solo",
+ "tags": [
+ "양손",
+ "도입부",
+ "마무리",
+ "슈만",
+ "중급",
+ "CC BY-SA"
+ ],
+ "source_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-12-Pere-Fouettard/schumann-op68-12-Pere-Fouettard.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-12-Pere-Fouettard/schumann-op68-12-Pere-Fouettard.rdf",
+ "source_title": "Album pour la jeunesse - 12.Le Père Fouettard",
+ "source_id": "Mutopia-2007/02/11-784",
+ "maintainer": "Philippe Hézaine",
+ "license": "Creative Commons Attribution-ShareAlike 2.5",
+ "duration_seconds": 117.7
+ }
+]
diff --git a/backend/sample_midi/starlight-steps-beginner.mid b/backend/sample_midi/starlight-steps-beginner.mid
deleted file mode 100644
index 6cf2f29..0000000
Binary files a/backend/sample_midi/starlight-steps-beginner.mid and /dev/null differ
diff --git a/backend/sample_midi/starlight-steps-easy.mid b/backend/sample_midi/starlight-steps-easy.mid
deleted file mode 100644
index 0835035..0000000
Binary files a/backend/sample_midi/starlight-steps-easy.mid and /dev/null differ
diff --git a/backend/sample_midi/sunrise-arpeggio-easy.mid b/backend/sample_midi/sunrise-arpeggio-easy.mid
deleted file mode 100644
index 9bd42f2..0000000
Binary files a/backend/sample_midi/sunrise-arpeggio-easy.mid and /dev/null differ
diff --git a/backend/tests/test_library_db.py b/backend/tests/test_library_db.py
index 4c6e049..5f10bc6 100644
--- a/backend/tests/test_library_db.py
+++ b/backend/tests/test_library_db.py
@@ -57,14 +57,9 @@ class LibraryDatabaseTest(unittest.TestCase):
self.assertNotIn("오른손", sample["tags"])
self.assertNotIn("쉬운 반주", sample["tags"])
- def test_self_composed_beginner_midis_are_seeded_as_public(self) -> None:
- names = {
- "starlight-steps-beginner.mid",
- "starlight-steps-easy.mid",
- "evening-waltz-beginner.mid",
- "sunrise-arpeggio-easy.mid",
- "black-key-pentatonic-beginner.mid",
- }
+ def test_practice_collection_is_seeded_as_public(self) -> None:
+ names = {f"practice-burgmuller-{number:02}.mid" for number in range(1, 19)}
+ names.update(f"practice-schumann-{number:02}.mid" for number in range(1, 13))
with self.db.connect() as connection:
rows = connection.execute(
"SELECT seed_key, visibility, min_tier, owner_id FROM midi_items WHERE seed_key IS NOT NULL"
@@ -76,6 +71,7 @@ class LibraryDatabaseTest(unittest.TestCase):
self.assertEqual(row["min_tier"], 0)
self.assertIsNone(row["owner_id"])
self.assertTrue(Path(self.temp.name, "midi", name).is_file())
+ self.assertFalse(any(row["seed_key"] in self.db.LEGACY_SAMPLE_KEYS for row in rows))
if __name__ == "__main__":
diff --git a/backend/tools/generate_beginner_midis.py b/backend/tools/generate_beginner_midis.py
deleted file mode 100644
index a15c12d..0000000
--- a/backend/tools/generate_beginner_midis.py
+++ /dev/null
@@ -1,180 +0,0 @@
-"""초보자용 양손 연습곡을 두 트랙 MIDI로 다시 만든다."""
-
-from __future__ import annotations
-
-from pathlib import Path
-
-
-OUTPUT = Path(__file__).resolve().parents[1] / "sample_midi"
-TICKS = 480
-Note = tuple[int, int, int, int]
-
-
-def variable_length(value: int) -> bytes:
- if value < 0:
- raise ValueError("MIDI 시간 간격은 음수일 수 없습니다")
- data = [value & 0x7F]
- value >>= 7
- while value:
- data.insert(0, (value & 0x7F) | 0x80)
- value >>= 7
- return bytes(data)
-
-
-def make_track(name: str, notes: list[Note], channel: int, tempo: int | None = None,
- meter: int = 4) -> bytes:
- events: list[tuple[int, int, bytes]] = []
- if tempo is not None:
- events.append((0, 0, b"\xff\x51\x03" + tempo.to_bytes(3, "big")))
- events.append((0, 0, bytes((0xFF, 0x58, 0x04, meter, 2, 24, 8))))
- label = name.encode("utf-8")
- events.append((0, 0, bytes((0xFF, 0x03, len(label))) + label))
- events.append((0, 0, bytes((0xC0 | channel, 0))))
- for start, duration, pitch, velocity in notes:
- if start < 0 or duration <= 0 or not 21 <= pitch <= 108:
- raise ValueError("연습곡의 음표 범위가 올바르지 않습니다")
- events.append((start, 2, bytes((0x90 | channel, pitch, velocity))))
- # 같은 음을 연달아 연주할 때 앞 음을 먼저 끝내야 브라우저에서 구분된다.
- events.append((start + duration, 1, bytes((0x80 | channel, pitch, 0))))
- events.sort(key=lambda event: (event[0], event[1], event[2]))
- body = bytearray()
- previous = 0
- for tick, _, message in events:
- body.extend(variable_length(tick - previous))
- body.extend(message)
- previous = tick
- body.extend(b"\x00\xff\x2f\x00")
- return b"MTrk" + len(body).to_bytes(4, "big") + body
-
-
-def write(name: str, right: list[Note], left: list[Note], bpm: int, meter: int = 4) -> None:
- if not right or not left:
- raise ValueError("양손 연습곡에는 양쪽 손의 음표가 모두 필요합니다")
- header = b"MThd" + (6).to_bytes(4, "big") + (1).to_bytes(2, "big") + (2).to_bytes(2, "big") + TICKS.to_bytes(2, "big")
- song = header + make_track("Right Hand", right, 0, round(60_000_000 / bpm), meter) + make_track("Left Hand", left, 1)
- (OUTPUT / name).write_bytes(song)
-
-
-def melody_bars(bars: list[list[tuple[int, float]]], meter: int) -> list[Note]:
- notes: list[Note] = []
- for index, bar in enumerate(bars):
- offset = 0
- for pitch, beats in bar:
- duration = round(beats * TICKS)
- notes.append((index * meter * TICKS + offset, max(1, duration - 24), pitch, 82))
- offset += duration
- if offset != meter * TICKS:
- raise ValueError("한 마디의 박자가 맞지 않습니다")
- return notes
-
-
-def starlight(easy: bool) -> tuple[list[Note], list[Note]]:
- opening = [
- [(64, 1), (67, 1), (72, 2)], [(69, 1), (67, 1), (64, 2)],
- [(65, 1), (69, 1), (72, 2)], [(71, 1), (69, 1), (67, 2)],
- [(64, 1), (67, 1), (72, 2)], [(76, 1), (72, 1), (69, 2)],
- [(72, 1), (71, 1), (69, 2)], [(67, 1), (64, 1), (60, 2)],
- ]
- middle = [
- [(67, 1), (72, 1), (76, 2)], [(72, 1), (69, 1), (64, 2)],
- [(69, 1), (72, 1), (77, 2)], [(74, 1), (71, 1), (67, 2)],
- [(64, 1), (67, 1), (72, 2)], [(72, 1), (69, 1), (64, 2)],
- [(67, 1), (71, 1), (74, 2)], [(72, 1), (67, 1), (64, 2)],
- ]
- ending = opening[:-1] + [[(67, 1), (64, 1), (60, 2)]]
- right = melody_bars(opening + middle + ending, 4)
- chords = [
- (48, 52, 55), (45, 48, 52), (41, 45, 48), (43, 47, 50),
- (48, 52, 55), (45, 48, 52), (41, 45, 48), (48, 52, 55),
- ] * 3
- left: list[Note] = []
- for bar, (root, third, fifth) in enumerate(chords):
- start = bar * 4 * TICKS
- if easy:
- for beat, pitch in enumerate((root, fifth, third, fifth)):
- left.append((start + beat * TICKS, TICKS - 32, pitch, 65))
- else:
- left.extend(((start, 2 * TICKS - 32, root, 70),
- (start + 2 * TICKS, 2 * TICKS - 32, fifth, 64)))
- return right, left
-
-
-def evening_waltz() -> tuple[list[Note], list[Note]]:
- opening = [
- [(67, 1), (69, 1), (72, 1)], [(71, 1), (69, 1), (67, 1)],
- [(64, 1), (67, 1), (72, 1)], [(69, 1), (67, 1), (64, 1)],
- [(65, 1), (69, 1), (72, 1)], [(74, 1), (72, 1), (69, 1)],
- [(67, 1), (71, 1), (74, 1)], [(72, 1), (67, 1), (60, 1)],
- ]
- middle = [
- [(72, 1), (74, 1), (76, 1)], [(74, 1), (71, 1), (67, 1)],
- [(69, 1), (72, 1), (76, 1)], [(72, 1), (69, 1), (64, 1)],
- [(72, 1), (69, 1), (65, 1)], [(69, 1), (72, 1), (74, 1)],
- [(74, 1), (71, 1), (67, 1)], [(72, 1), (67, 1), (60, 1)],
- ]
- right = melody_bars(opening + middle + opening + opening, 3)
- basses = [(48, 55), (43, 50), (45, 52), (48, 55),
- (41, 48), (45, 52), (43, 50), (48, 55)] * 4
- left: list[Note] = []
- for bar, (root, fifth) in enumerate(basses):
- start = bar * 3 * TICKS
- left.extend(((start, TICKS - 32, root, 71),
- (start + TICKS, TICKS - 32, fifth, 60),
- (start + 2 * TICKS, TICKS - 32, fifth, 60)))
- return right, left
-
-
-def sunrise_arpeggio() -> tuple[list[Note], list[Note]]:
- chords = [(60, 64, 67, 72), (57, 60, 64, 69), (53, 57, 60, 65),
- (55, 59, 62, 67), (60, 64, 67, 72), (57, 60, 64, 69),
- (53, 57, 60, 65), (60, 64, 67, 72)] * 2 + [
- (53, 57, 60, 65), (55, 59, 62, 67),
- (57, 60, 64, 69), (60, 64, 67, 72),
- ]
- right: list[Note] = []
- left: list[Note] = []
- for bar, tones in enumerate(chords):
- start = bar * 4 * TICKS
- # 한 손은 네 음을 차례로 누르고 다른 손은 긴 근음 하나만 유지한다.
- for beat, pitch in enumerate(tones):
- right.append((start + beat * TICKS, TICKS - 36, pitch, 75 + beat * 3))
- left.append((start, 4 * TICKS - 48, tones[0] - 12, 62))
- return right, left
-
-
-def black_keys() -> tuple[list[Note], list[Note]]:
- opening = [
- [(66, 1), (68, 1), (70, 2)], [(73, 1), (70, 1), (68, 2)],
- [(66, 1), (70, 1), (73, 2)], [(75, 1), (73, 1), (70, 2)],
- [(68, 1), (70, 1), (73, 2)], [(75, 1), (73, 1), (70, 2)],
- [(68, 1), (66, 1), (70, 2)], [(73, 1), (70, 1), (66, 2)],
- ]
- middle = [
- [(70, 1), (73, 1), (75, 2)], [(78, 1), (75, 1), (73, 2)],
- [(70, 1), (68, 1), (66, 2)], [(68, 1), (70, 1), (73, 2)],
- [(75, 1), (73, 1), (70, 2)], [(68, 1), (70, 1), (75, 2)],
- [(73, 1), (70, 1), (68, 2)], [(66, 1), (68, 1), (70, 2)],
- ]
- right = melody_bars(opening + middle + opening, 4)
- left: list[Note] = []
- for bar in range(24):
- start = bar * 4 * TICKS
- left.extend(((start, 2 * TICKS - 48, 42, 61),
- (start + 2 * TICKS, 2 * TICKS - 48, 49, 59)))
- return right, left
-
-
-def main() -> None:
- OUTPUT.mkdir(parents=True, exist_ok=True)
- for name, song, bpm, meter in (
- ("starlight-steps-beginner.mid", starlight(False), 76, 4),
- ("starlight-steps-easy.mid", starlight(True), 84, 4),
- ("evening-waltz-beginner.mid", evening_waltz(), 90, 3),
- ("sunrise-arpeggio-easy.mid", sunrise_arpeggio(), 72, 4),
- ("black-key-pentatonic-beginner.mid", black_keys(), 78, 4),
- ):
- write(name, *song, bpm, meter)
-
-
-if __name__ == "__main__":
- main()
diff --git a/backend/tools/practice_sources/burgmuller-01.mid b/backend/tools/practice_sources/burgmuller-01.mid
new file mode 100644
index 0000000..4dbd990
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-01.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-01.rdf b/backend/tools/practice_sources/burgmuller-01.rdf
new file mode 100644
index 0000000..fcc5153
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-01.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ La Candeur
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro moderato
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-01.ly
+ 25EF-01.mid
+ 25EF-01-a4.ps.gz
+ 25EF-01-a4.pdf
+ 25EF-01-let.ps.gz
+ 25EF-01-let.pdf
+ 25EF-01-preview.png
+ 84
+ 539
+
+ Mutopia-2013/01/12-202
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-02.mid b/backend/tools/practice_sources/burgmuller-02.mid
new file mode 100644
index 0000000..19450b1
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-02.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-02.rdf b/backend/tools/practice_sources/burgmuller-02.rdf
new file mode 100644
index 0000000..6547402
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-02.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ l'Arabesque
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro scherzando
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-02.ly
+ 25EF-02.mid
+ 25EF-02-a4.ps.gz
+ 25EF-02-a4.pdf
+ 25EF-02-let.ps.gz
+ 25EF-02-let.pdf
+ 25EF-02-preview.png
+ 94
+ 539
+
+ Mutopia-2013/01/12-203
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-03.mid b/backend/tools/practice_sources/burgmuller-03.mid
new file mode 100644
index 0000000..bb2e309
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-03.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-03.rdf b/backend/tools/practice_sources/burgmuller-03.rdf
new file mode 100644
index 0000000..2043cdc
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-03.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ La Pastorale
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Andantino
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-03.ly
+ 25EF-03.mid
+ 25EF-03-a4.ps.gz
+ 25EF-03-a4.pdf
+ 25EF-03-let.ps.gz
+ 25EF-03-let.pdf
+ 25EF-03-preview.png
+ 101
+ 539
+
+ Mutopia-2013/01/12-218
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-04.mid b/backend/tools/practice_sources/burgmuller-04.mid
new file mode 100644
index 0000000..553f736
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-04.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-04.rdf b/backend/tools/practice_sources/burgmuller-04.rdf
new file mode 100644
index 0000000..0742d9d
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-04.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ La Petite Réunion
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro non troppo
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-04.ly
+ 25EF-04.mid
+ 25EF-04-a4.ps.gz
+ 25EF-04-a4.pdf
+ 25EF-04-let.ps.gz
+ 25EF-04-let.pdf
+ 25EF-04-preview.png
+ 88
+ 539
+
+ Mutopia-2013/01/12-219
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-05.mid b/backend/tools/practice_sources/burgmuller-05.mid
new file mode 100644
index 0000000..61bc68e
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-05.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-05.rdf b/backend/tools/practice_sources/burgmuller-05.rdf
new file mode 100644
index 0000000..869195b
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-05.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Innocence
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Moderato
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-05.ly
+ 25EF-05.mid
+ 25EF-05-a4.ps.gz
+ 25EF-05-a4.pdf
+ 25EF-05-let.ps.gz
+ 25EF-05-let.pdf
+ 25EF-05-preview.png
+ 93
+ 539
+
+ Mutopia-2013/01/12-214
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-06.mid b/backend/tools/practice_sources/burgmuller-06.mid
new file mode 100644
index 0000000..2f4faf7
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-06.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-06.rdf b/backend/tools/practice_sources/burgmuller-06.rdf
new file mode 100644
index 0000000..0b0a94a
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-06.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Progrès
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-06.ly
+ 25EF-06.mid
+ 25EF-06-a4.ps.gz
+ 25EF-06-a4.pdf
+ 25EF-06-let.ps.gz
+ 25EF-06-let.pdf
+ 25EF-06-preview.png
+ 90
+ 539
+
+ Mutopia-2013/01/12-215
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-07.mid b/backend/tools/practice_sources/burgmuller-07.mid
new file mode 100644
index 0000000..9ea7e8f
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-07.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-07.rdf b/backend/tools/practice_sources/burgmuller-07.rdf
new file mode 100644
index 0000000..6fd73c1
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-07.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Le Courant Limpide
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro vivace
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-07.ly
+ 25EF-07.mid
+ 25EF-07-a4.ps.gz
+ 25EF-07-a4.pdf
+ 25EF-07-let.ps.gz
+ 25EF-07-let.pdf
+ 25EF-07-preview.png
+ 93
+ 539
+
+ Mutopia-2013/01/12-216
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-08.mid b/backend/tools/practice_sources/burgmuller-08.mid
new file mode 100644
index 0000000..613864d
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-08.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-08.rdf b/backend/tools/practice_sources/burgmuller-08.rdf
new file mode 100644
index 0000000..d2c5ed9
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-08.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ La Gracieuse
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Moderato
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-08.ly
+ 25EF-08.mid
+ 25EF-08-a4.ps.gz
+ 25EF-08-a4.pdf
+ 25EF-08-let.ps.gz
+ 25EF-08-let.pdf
+ 25EF-08-preview.png
+ 101
+ 539
+
+ Mutopia-2013/01/12-217
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-09.mid b/backend/tools/practice_sources/burgmuller-09.mid
new file mode 100644
index 0000000..5a6fb0e
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-09.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-09.rdf b/backend/tools/practice_sources/burgmuller-09.rdf
new file mode 100644
index 0000000..ee6cd1a
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-09.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ La Chasse
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro vivace
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-09.ly
+ 25EF-09.mid
+ 25EF-09-a4.ps.gz
+ 25EF-09-a4.pdf
+ 25EF-09-let.ps.gz
+ 25EF-09-let.pdf
+ 25EF-09-preview.png
+ 104
+ 539
+
+ Mutopia-2013/01/12-220
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-10.mid b/backend/tools/practice_sources/burgmuller-10.mid
new file mode 100644
index 0000000..bdcf694
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-10.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-10.rdf b/backend/tools/practice_sources/burgmuller-10.rdf
new file mode 100644
index 0000000..1ed3323
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-10.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Tendre Fleur
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Moderato
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-10.ly
+ 25EF-10.mid
+ 25EF-10-a4.ps.gz
+ 25EF-10-a4.pdf
+ 25EF-10-let.ps.gz
+ 25EF-10-let.pdf
+ 25EF-10-preview.png
+ 101
+ 539
+
+ Mutopia-2013/01/12-221
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-11.mid b/backend/tools/practice_sources/burgmuller-11.mid
new file mode 100644
index 0000000..2f17051
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-11.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-11.rdf b/backend/tools/practice_sources/burgmuller-11.rdf
new file mode 100644
index 0000000..deb96bd
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-11.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ La Bergeronnette
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegretto
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-11.ly
+ 25EF-11.mid
+ 25EF-11-a4.ps.gz
+ 25EF-11-a4.pdf
+ 25EF-11-let.ps.gz
+ 25EF-11-let.pdf
+ 25EF-11-preview.png
+ 106
+ 539
+
+ Mutopia-2013/01/12-222
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-12.mid b/backend/tools/practice_sources/burgmuller-12.mid
new file mode 100644
index 0000000..013aa86
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-12.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-12.rdf b/backend/tools/practice_sources/burgmuller-12.rdf
new file mode 100644
index 0000000..ce066ef
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-12.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ L'Adieu
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro molto agitato
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-12.ly
+ 25EF-12.mid
+ 25EF-12-a4.ps.gz
+ 25EF-12-a4.pdf
+ 25EF-12-let.ps.gz
+ 25EF-12-let.pdf
+ 25EF-12-preview.png
+ 90
+ 539
+
+ Mutopia-2013/01/12-223
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-13.mid b/backend/tools/practice_sources/burgmuller-13.mid
new file mode 100644
index 0000000..afcd8e4
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-13.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-13.rdf b/backend/tools/practice_sources/burgmuller-13.rdf
new file mode 100644
index 0000000..b93de3b
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-13.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Consolation
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro moderato
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-13.ly
+ 25EF-13.mid
+ 25EF-13-a4.ps.gz
+ 25EF-13-a4.pdf
+ 25EF-13-let.ps.gz
+ 25EF-13-let.pdf
+ 25EF-13-preview.png
+ 103
+ 539
+
+ Mutopia-2013/01/12-224
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-14.mid b/backend/tools/practice_sources/burgmuller-14.mid
new file mode 100644
index 0000000..40f6e09
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-14.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-14.rdf b/backend/tools/practice_sources/burgmuller-14.rdf
new file mode 100644
index 0000000..e9dd291
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-14.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ La Styrienne
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Tempo di Valse
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-14.ly
+ 25EF-14.mid
+ 25EF-14-a4.ps.gz
+ 25EF-14-a4.pdf
+ 25EF-14-let.ps.gz
+ 25EF-14-let.pdf
+ 25EF-14-preview.png
+ 91
+ 539
+
+ Mutopia-2013/01/12-225
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-15.mid b/backend/tools/practice_sources/burgmuller-15.mid
new file mode 100644
index 0000000..2505591
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-15.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-15.rdf b/backend/tools/practice_sources/burgmuller-15.rdf
new file mode 100644
index 0000000..8404926
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-15.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Ballade
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro con brio
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-15.ly
+ 25EF-15.mid
+ 25EF-15-a4.ps.gz
+ 25EF-15-a4.pdf
+ 25EF-15-let.ps.gz
+ 25EF-15-let.pdf
+ 25EF-15-preview.png
+ 91
+ 539
+
+ Mutopia-2013/01/12-227
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-16.mid b/backend/tools/practice_sources/burgmuller-16.mid
new file mode 100644
index 0000000..13074ff
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-16.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-16.rdf b/backend/tools/practice_sources/burgmuller-16.rdf
new file mode 100644
index 0000000..d278c55
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-16.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Douce Plainte
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro moderato
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-16.ly
+ 25EF-16.mid
+ 25EF-16-a4.ps.gz
+ 25EF-16-a4.pdf
+ 25EF-16-let.ps.gz
+ 25EF-16-let.pdf
+ 25EF-16-preview.png
+ 79
+ 539
+
+ Mutopia-2013/01/12-228
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-17.mid b/backend/tools/practice_sources/burgmuller-17.mid
new file mode 100644
index 0000000..fa6f723
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-17.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-17.rdf b/backend/tools/practice_sources/burgmuller-17.rdf
new file mode 100644
index 0000000..592aa78
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-17.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ La Babillarde
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro moderato
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-17.ly
+ 25EF-17.mid
+ 25EF-17-a4.ps.gz
+ 25EF-17-a4.pdf
+ 25EF-17-let.ps.gz
+ 25EF-17-let.pdf
+ 25EF-17-preview.png
+ 107
+ 539
+
+ Mutopia-2013/01/12-229
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/burgmuller-18.mid b/backend/tools/practice_sources/burgmuller-18.mid
new file mode 100644
index 0000000..3cd6b71
Binary files /dev/null and b/backend/tools/practice_sources/burgmuller-18.mid differ
diff --git a/backend/tools/practice_sources/burgmuller-18.rdf b/backend/tools/practice_sources/burgmuller-18.rdf
new file mode 100644
index 0000000..cfe38d6
--- /dev/null
+++ b/backend/tools/practice_sources/burgmuller-18.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Inquiétude
+ BurgmullerJFF
+ Opus 100.
+
+ Piano
+ 19th Century
+ Romantic
+ Allegro agitato
+
+ Collection Litolff, 19th Century
+ Public Domain
+
+ 25EF-18.ly
+ 25EF-18.mid
+ 25EF-18-a4.ps.gz
+ 25EF-18-a4.pdf
+ 25EF-18-let.ps.gz
+ 25EF-18-let.pdf
+ 25EF-18-preview.png
+ 86
+ 539
+
+ Mutopia-2013/01/12-230
+ Bas Wassink
+ basvanlola@zonnet.com
+
+
+ 2.16.1
+
+
+
diff --git a/backend/tools/practice_sources/catalog.json b/backend/tools/practice_sources/catalog.json
new file mode 100644
index 0000000..3f56ef5
--- /dev/null
+++ b/backend/tools/practice_sources/catalog.json
@@ -0,0 +1,182 @@
+[
+ {
+ "name": "burgmuller-01",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-01/25EF-01.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-01/25EF-01.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-02",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-02/25EF-02.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-02/25EF-02.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-03",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-03/25EF-03.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-03/25EF-03.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-04",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-04/25EF-04.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-04/25EF-04.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-05",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-05/25EF-05.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-05/25EF-05.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-06",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-06/25EF-06.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-06/25EF-06.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-07",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-07/25EF-07.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-07/25EF-07.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-08",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-08/25EF-08.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-08/25EF-08.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-09",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-09/25EF-09.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-09/25EF-09.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-10",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-10/25EF-10.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-10/25EF-10.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-11",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-11/25EF-11.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-11/25EF-11.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-12",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-12/25EF-12.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-12/25EF-12.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-13",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-13/25EF-13.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-13/25EF-13.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-14",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-14/25EF-14.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-14/25EF-14.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-15",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-15/25EF-15.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-15/25EF-15.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-16",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-16/25EF-16.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-16/25EF-16.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-17",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-17/25EF-17.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-17/25EF-17.rdf",
+ "rights": []
+ },
+ {
+ "name": "burgmuller-18",
+ "midi_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-18/25EF-18.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/BurgmullerJFF/O100/25EF-18/25EF-18.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-01",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-01-melodie/schumann-op68-01-melodie.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-01-melodie/schumann-op68-01-melodie.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-02",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-02-marche-militaire/schumann-op68-02-marche-militaire.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-02-marche-militaire/schumann-op68-02-marche-militaire.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-03",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-03-chanson-fredonnee/schumann-op68-03-chanson-fredonnee.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-03-chanson-fredonnee/schumann-op68-03-chanson-fredonnee.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-04",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-04-choral/schumann-op68-04-choral.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-04-choral/schumann-op68-04-choral.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-05",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-05-petite-piece/schumann-op68-05-petite-piece.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-05-petite-piece/schumann-op68-05-petite-piece.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-06",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-06-pauvre-orpheline/schumann-op68-06-pauvre-orpheline.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-06-pauvre-orpheline/schumann-op68-06-pauvre-orpheline.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-07",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-07-chanson-du-chasseur/schumann-op68-07-chanson-du-chasseur.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-07-chanson-du-chasseur/schumann-op68-07-chanson-du-chasseur.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-08",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-08-cavalier-sauvage/schumann-op68-08-cavalier-sauvage.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-08-cavalier-sauvage/schumann-op68-08-cavalier-sauvage.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-09",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-09-chanson-populaire/schumann-op68-09-chanson-populaire.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-09-chanson-populaire/schumann-op68-09-chanson-populaire.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-10",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-10-gai-laboureur/schumann-op68-10-gai-laboureur.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-10-gai-laboureur/schumann-op68-10-gai-laboureur.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-11",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-11-sicilienne/schumann-op68-11-sicilienne.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-11-sicilienne/schumann-op68-11-sicilienne.rdf",
+ "rights": []
+ },
+ {
+ "name": "schumann-12",
+ "midi_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-12-Pere-Fouettard/schumann-op68-12-Pere-Fouettard.mid",
+ "rdf_url": "https://www.mutopiaproject.org/ftp/SchumannR/O68/schumann-op68-12-Pere-Fouettard/schumann-op68-12-Pere-Fouettard.rdf",
+ "rights": []
+ }
+]
\ No newline at end of file
diff --git a/backend/tools/practice_sources/schumann-01.mid b/backend/tools/practice_sources/schumann-01.mid
new file mode 100644
index 0000000..6f77000
Binary files /dev/null and b/backend/tools/practice_sources/schumann-01.mid differ
diff --git a/backend/tools/practice_sources/schumann-01.rdf b/backend/tools/practice_sources/schumann-01.rdf
new file mode 100644
index 0000000..1754ccc
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-01.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 1.Mélodie
+ SchumannR
+ O 68 n°1
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-01-melodie.ly
+ schumann-op68-01-melodie.mid
+ schumann-op68-01-melodie-a4.ps.gz
+ schumann-op68-01-melodie-a4.pdf
+ schumann-op68-01-melodie-let.ps.gz
+ schumann-op68-01-melodie-let.pdf
+ schumann-op68-01-melodie-preview.png
+ 125
+ 539
+
+ Mutopia-2007/02/11-647
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/backend/tools/practice_sources/schumann-02.mid b/backend/tools/practice_sources/schumann-02.mid
new file mode 100644
index 0000000..420678d
Binary files /dev/null and b/backend/tools/practice_sources/schumann-02.mid differ
diff --git a/backend/tools/practice_sources/schumann-02.rdf b/backend/tools/practice_sources/schumann-02.rdf
new file mode 100644
index 0000000..b038d26
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-02.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 2.Marche militaire
+ SchumannR
+ O 68 n°2
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-02-marche-militaire.ly
+ schumann-op68-02-marche-militaire.mid
+ schumann-op68-02-marche-militaire-a4.ps.gz
+ schumann-op68-02-marche-militaire-a4.pdf
+ schumann-op68-02-marche-militaire-let.ps.gz
+ schumann-op68-02-marche-militaire-let.pdf
+ schumann-op68-02-marche-militaire-preview.png
+ 107
+ 539
+
+ Mutopia-2007/02/11-650
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/backend/tools/practice_sources/schumann-03.mid b/backend/tools/practice_sources/schumann-03.mid
new file mode 100644
index 0000000..8a0f12b
Binary files /dev/null and b/backend/tools/practice_sources/schumann-03.mid differ
diff --git a/backend/tools/practice_sources/schumann-03.rdf b/backend/tools/practice_sources/schumann-03.rdf
new file mode 100644
index 0000000..aa0073d
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-03.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 3.Chanson fredonnée
+ SchumannR
+ O 68 n°3
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 3.0
+
+ schumann-op68-03-chanson-fredonnee.ly
+ schumann-op68-03-chanson-fredonnee.mid
+ schumann-op68-03-chanson-fredonnee-a4.ps.gz
+ schumann-op68-03-chanson-fredonnee-a4.pdf
+ schumann-op68-03-chanson-fredonnee-let.ps.gz
+ schumann-op68-03-chanson-fredonnee-let.pdf
+ schumann-op68-03-chanson-fredonnee-preview.png
+ 135
+ 543
+
+ Mutopia-2011/09/18-651
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.12.3
+
+
+
diff --git a/backend/tools/practice_sources/schumann-04.mid b/backend/tools/practice_sources/schumann-04.mid
new file mode 100644
index 0000000..398b398
Binary files /dev/null and b/backend/tools/practice_sources/schumann-04.mid differ
diff --git a/backend/tools/practice_sources/schumann-04.rdf b/backend/tools/practice_sources/schumann-04.rdf
new file mode 100644
index 0000000..e54f50f
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-04.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 4.Un choral
+ SchumannR
+ O 68 n°4
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-04-choral.ly
+ schumann-op68-04-choral.mid
+ schumann-op68-04-choral-a4.ps.gz
+ schumann-op68-04-choral-a4.pdf
+ schumann-op68-04-choral-let.ps.gz
+ schumann-op68-04-choral-let.pdf
+ schumann-op68-04-choral-preview.png
+ 124
+ 539
+
+ Mutopia-2007/02/11-782
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/backend/tools/practice_sources/schumann-05.mid b/backend/tools/practice_sources/schumann-05.mid
new file mode 100644
index 0000000..70cf232
Binary files /dev/null and b/backend/tools/practice_sources/schumann-05.mid differ
diff --git a/backend/tools/practice_sources/schumann-05.rdf b/backend/tools/practice_sources/schumann-05.rdf
new file mode 100644
index 0000000..dd03957
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-05.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 5.Petite pièce
+ SchumannR
+ O 68 n°5
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-05-petite-piece.ly
+ schumann-op68-05-petite-piece.mid
+ schumann-op68-05-petite-piece-a4.ps.gz
+ schumann-op68-05-petite-piece-a4.pdf
+ schumann-op68-05-petite-piece-let.ps.gz
+ schumann-op68-05-petite-piece-let.pdf
+ schumann-op68-05-petite-piece-preview.png
+ 129
+ 539
+
+ Mutopia-2007/02/11-653
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/backend/tools/practice_sources/schumann-06.mid b/backend/tools/practice_sources/schumann-06.mid
new file mode 100644
index 0000000..8a75978
Binary files /dev/null and b/backend/tools/practice_sources/schumann-06.mid differ
diff --git a/backend/tools/practice_sources/schumann-06.rdf b/backend/tools/practice_sources/schumann-06.rdf
new file mode 100644
index 0000000..39cd1fe
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-06.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 6.Pauvre orpheline
+ SchumannR
+ O 68 n°6
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-06-pauvre-orpheline.ly
+ schumann-op68-06-pauvre-orpheline.mid
+ schumann-op68-06-pauvre-orpheline-a4.ps.gz
+ schumann-op68-06-pauvre-orpheline-a4.pdf
+ schumann-op68-06-pauvre-orpheline-let.ps.gz
+ schumann-op68-06-pauvre-orpheline-let.pdf
+ schumann-op68-06-pauvre-orpheline-preview.png
+ 123
+ 539
+
+ Mutopia-2007/02/11-687
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/backend/tools/practice_sources/schumann-07.mid b/backend/tools/practice_sources/schumann-07.mid
new file mode 100644
index 0000000..f0313bd
Binary files /dev/null and b/backend/tools/practice_sources/schumann-07.mid differ
diff --git a/backend/tools/practice_sources/schumann-07.rdf b/backend/tools/practice_sources/schumann-07.rdf
new file mode 100644
index 0000000..6e28c94
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-07.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 7.Chanson du chasseur
+ SchumannR
+ O 68 n°7
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 3.0
+
+ schumann-op68-07-chanson-du-chasseur.ly
+ schumann-op68-07-chanson-du-chasseur.mid
+ schumann-op68-07-chanson-du-chasseur-a4.ps.gz
+ schumann-op68-07-chanson-du-chasseur-a4.pdf
+ schumann-op68-07-chanson-du-chasseur-let.ps.gz
+ schumann-op68-07-chanson-du-chasseur-let.pdf
+ schumann-op68-07-chanson-du-chasseur-preview.png
+ 145
+ 539
+
+ Mutopia-2007/05/20-691
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.23
+
+
+
diff --git a/backend/tools/practice_sources/schumann-08.mid b/backend/tools/practice_sources/schumann-08.mid
new file mode 100644
index 0000000..4dbf9cc
Binary files /dev/null and b/backend/tools/practice_sources/schumann-08.mid differ
diff --git a/backend/tools/practice_sources/schumann-08.rdf b/backend/tools/practice_sources/schumann-08.rdf
new file mode 100644
index 0000000..8404e15
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-08.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 8.Le cavalier sauvage
+ SchumannR
+ O 68 n°8
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-08-cavalier-sauvage.ly
+ schumann-op68-08-cavalier-sauvage.mid
+ schumann-op68-08-cavalier-sauvage-a4.ps.gz
+ schumann-op68-08-cavalier-sauvage-a4.pdf
+ schumann-op68-08-cavalier-sauvage-let.ps.gz
+ schumann-op68-08-cavalier-sauvage-let.pdf
+ schumann-op68-08-cavalier-sauvage-preview.png
+ 113
+ 539
+
+ Mutopia-2007/02/11-655
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/backend/tools/practice_sources/schumann-09.mid b/backend/tools/practice_sources/schumann-09.mid
new file mode 100644
index 0000000..be18e76
Binary files /dev/null and b/backend/tools/practice_sources/schumann-09.mid differ
diff --git a/backend/tools/practice_sources/schumann-09.rdf b/backend/tools/practice_sources/schumann-09.rdf
new file mode 100644
index 0000000..843499f
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-09.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 9.Chanson populaire
+ SchumannR
+ O 68 n°9
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-09-chanson-populaire.ly
+ schumann-op68-09-chanson-populaire.mid
+ schumann-op68-09-chanson-populaire-a4.ps.gz
+ schumann-op68-09-chanson-populaire-a4.pdf
+ schumann-op68-09-chanson-populaire-let.ps.gz
+ schumann-op68-09-chanson-populaire-let.pdf
+ schumann-op68-09-chanson-populaire-preview.png
+ 116
+ 539
+
+ Mutopia-2007/02/11-675
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/backend/tools/practice_sources/schumann-10.mid b/backend/tools/practice_sources/schumann-10.mid
new file mode 100644
index 0000000..87f6f1f
Binary files /dev/null and b/backend/tools/practice_sources/schumann-10.mid differ
diff --git a/backend/tools/practice_sources/schumann-10.rdf b/backend/tools/practice_sources/schumann-10.rdf
new file mode 100644
index 0000000..e76de63
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-10.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 10.Le gai laboureur
+ SchumannR
+ O 68 n°10
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-10-gai-laboureur.ly
+ schumann-op68-10-gai-laboureur.mid
+ schumann-op68-10-gai-laboureur-a4.ps.gz
+ schumann-op68-10-gai-laboureur-a4.pdf
+ schumann-op68-10-gai-laboureur-let.ps.gz
+ schumann-op68-10-gai-laboureur-let.pdf
+ schumann-op68-10-gai-laboureur-preview.png
+ 107
+ 549
+
+ Mutopia-2007/02/11-659
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/backend/tools/practice_sources/schumann-11.mid b/backend/tools/practice_sources/schumann-11.mid
new file mode 100644
index 0000000..349b77d
Binary files /dev/null and b/backend/tools/practice_sources/schumann-11.mid differ
diff --git a/backend/tools/practice_sources/schumann-11.rdf b/backend/tools/practice_sources/schumann-11.rdf
new file mode 100644
index 0000000..8d6f747
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-11.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 11.Sicilienne
+ SchumannR
+ O 68 n°11
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-11-sicilienne.ly
+ schumann-op68-11-sicilienne.mid
+ schumann-op68-11-sicilienne-a4.ps.gz
+ schumann-op68-11-sicilienne-a4.pdf
+ schumann-op68-11-sicilienne-let.ps.gz
+ schumann-op68-11-sicilienne-let.pdf
+ schumann-op68-11-sicilienne-preview.png
+ 108
+ 549
+
+ Mutopia-2007/02/11-783
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/backend/tools/practice_sources/schumann-12.mid b/backend/tools/practice_sources/schumann-12.mid
new file mode 100644
index 0000000..0a9b244
Binary files /dev/null and b/backend/tools/practice_sources/schumann-12.mid differ
diff --git a/backend/tools/practice_sources/schumann-12.rdf b/backend/tools/practice_sources/schumann-12.rdf
new file mode 100644
index 0000000..4e18ead
--- /dev/null
+++ b/backend/tools/practice_sources/schumann-12.rdf
@@ -0,0 +1,36 @@
+
+
+
+
+ Album pour la jeunesse - 12.Le Père Fouettard
+ SchumannR
+ O 68 n°12
+
+ Piano
+ 1848
+ Romantic
+
+
+ Peters
+ Creative Commons Attribution-ShareAlike 2.5
+
+ schumann-op68-12-Pere-Fouettard.ly
+ schumann-op68-12-Pere-Fouettard.mid
+ schumann-op68-12-Pere-Fouettard-a4.ps.gz
+ schumann-op68-12-Pere-Fouettard-a4.pdf
+ schumann-op68-12-Pere-Fouettard-let.ps.gz
+ schumann-op68-12-Pere-Fouettard-let.pdf
+ schumann-op68-12-Pere-Fouettard-preview.png
+ 122
+ 549
+
+ Mutopia-2007/02/11-784
+ Philippe Hézaine
+ philippe.hezaine@free.fr
+
+
+ 2.10.16
+
+
+
diff --git a/docs/LIBRARY-AUTH-KO.md b/docs/LIBRARY-AUTH-KO.md
index 399fe27..9c477fd 100644
--- a/docs/LIBRARY-AUTH-KO.md
+++ b/docs/LIBRARY-AUTH-KO.md
@@ -74,7 +74,7 @@ Mutopia Project가 공개 도메인으로 배포하는 실제 클래식 MIDI를
- 미뉴에트 내림마장조 WoO 82: 쉬운 양손 편곡과 중급 양손 원곡
- 엘리제를 위하여 WoO 59: 입문 양손과 쉬운 양손 및 고급 원곡
-같은 제목의 난이도별 버전은 한 묶음에서 선택한다. 기본 단계별 연습곡은 모두 오른손과 왼손을 포함한 피아노 독주로 제공한다. 손별 연습은 연습 화면에서 선택한다. 공개 도메인 원곡에서 만든 단계별 연습 편곡은 `scripts/build_sample_variants.mjs`로 다시 만들 수 있다. 자체 제작한 약 1분 길이의 초보자용 MIDI 다섯 파일은 `python backend/tools/generate_beginner_midis.py`로 다시 만들 수 있다.
+같은 제목의 난이도별 버전은 한 묶음에서 선택한다. 기본 단계별 연습곡은 모두 오른손과 왼손을 포함한 피아노 독주로 제공한다. 손별 연습은 연습 화면에서 선택한다. 공개 도메인 원곡에서 만든 단계별 연습 편곡은 `scripts/build_sample_variants.mjs`로 다시 만들 수 있다. 별도로 등록한 30곡의 양손 연습 편곡은 `node scripts/build_practice_collection.mjs`로 다시 만들 수 있다.
## 악보 표시
diff --git a/docs/THIRD_PARTY_SAMPLES.md b/docs/THIRD_PARTY_SAMPLES.md
index 27dcdbf..33abd5e 100644
--- a/docs/THIRD_PARTY_SAMPLES.md
+++ b/docs/THIRD_PARTY_SAMPLES.md
@@ -26,16 +26,13 @@ Mutopia Project의 배포 안내는
편곡 파일은 `node scripts/build_sample_variants.mjs`로 다시 만들 수 있다. 원본 MIDI가 바뀌면 생성 결과도 달라지므로 반영 전에 파일 크기와 음표 수와 실제 재생을 다시 검사한다.
-## 자체 제작 초보자 연습곡
+## 도입부와 마무리가 있는 연습곡 30개
-아래 다섯 파일의 선율과 반주는 이 프로젝트에서 새로 구성했다. 기존 유튜브 영상의 곡과 MIDI 파일을 옮긴 자료가 아니다. 모든 파일은 오른손과 왼손을 나눈 양손 MIDI이며 한 곡이 약 1분 이상 이어진다. 손별 연습은 연습 화면에서 선택한다.
+서로 다른 원곡 30개를 바탕으로 오른손의 중심 선율과 왼손의 저음을 골라 양손 연습용으로 편곡했다. 각 MIDI에는 원곡 첫 선율을 천천히 들려주는 도입부와 마지막 선율을 다시 들려주고 종지음을 유지하는 마무리가 있다. 곡 길이는 69~134초다. 난이도는 오른손 음표 밀도에 따라 입문과 쉬움과 중급으로 나눈다. 복잡한 곡을 입문이라고 표시하지 않는다.
-| 표시 제목 | 난이도 | 익히는 기법 | 파일 |
-| --- | --- | --- | --- |
-| 별빛 계단 | 입문 | 왼손 근음과 5도 교대 | `starlight-steps-beginner.mid` |
-| 별빛 계단 | 쉬움 | 왼손 네 음 분산 화음 | `starlight-steps-easy.mid` |
-| 저녁빛 왈츠 | 입문 | 3박자 왼손 저음과 반복음 | `evening-waltz-beginner.mid` |
-| 해돋이 아르페지오 | 쉬움 | 오른손 네 음 아르페지오와 왼손 긴 근음 | `sunrise-arpeggio-easy.mid` |
-| 검은 건반 산책 | 입문 | 검은 건반 5음 음계와 왼손 5도 | `black-key-pentatonic-beginner.mid` |
+- 부르크뮐러 Op. 100의 서로 다른 연습곡 18개: 원본 MIDI와 RDF의 표시는 모두 Public Domain이다. 원본 조판자는 Bas Wassink이다.
+- 슈만 Op. 68의 서로 다른 소품 12개: 원본 MIDI의 조판자는 Philippe Hézaine이다. RDF에 표시된 각 곡의 [CC BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/) 또는 [CC BY-SA 3.0](https://creativecommons.org/licenses/by-sa/3.0/) 조건을 편곡본에도 적용한다.
-분산 화음과 아르페지오 기법을 정리할 때 참고한 강좌는 Bill Hilton의 [Six Piano Broken Chord And Arpeggio Techniques You Need To Know](https://www.youtube.com/watch?v=8AMZph76kqM)다. 이 링크는 기법 참고 출처이며 위 MIDI의 원곡이나 저작권 허가를 뜻하지 않는다. 새 MIDI는 `python backend/tools/generate_beginner_midis.py`로 같은 내용으로 다시 만들 수 있다.
+곡별 원문 제목과 원본 MIDI 주소와 RDF 주소와 조판자와 정확한 라이선스 버전은 [`practice_collection.json`](../backend/sample_midi/practice_collection.json)에 기록했다. 받은 원본 MIDI와 RDF는 [`practice_sources`](../backend/tools/practice_sources)에 함께 보관한다. MIDI는 `node scripts/build_practice_collection.mjs`로 다시 만들고 `node scripts/check_practice_collection.mjs`로 검증한다. 회원 업로드와 구분되는 기본 공개 자료만 교체한다.
+
+유튜브 영상은 이 30곡의 음원이나 MIDI 출처가 아니다. 공개 시청이 가능한 영상이라는 이유만으로 선율을 복제하지 않았다. 특정 쇼츠에 나온 곡을 그대로 옮기는 작업은 별도의 이용 허락과 원본 확인이 필요하다.
diff --git a/scripts/build_practice_collection.mjs b/scripts/build_practice_collection.mjs
new file mode 100644
index 0000000..ff9566c
--- /dev/null
+++ b/scripts/build_practice_collection.mjs
@@ -0,0 +1,113 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import midiPackage from '../frontend/node_modules/@tonejs/midi/build/Midi.js';
+
+const { Midi } = midiPackage;
+const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
+const sourceRoot = path.join(root, 'backend', 'tools', 'practice_sources');
+const outputRoot = path.join(root, 'backend', 'sample_midi');
+const catalog = JSON.parse(fs.readFileSync(path.join(sourceRoot, 'catalog.json'), 'utf8'));
+const names = {
+ burgmuller: ['순진', '아라베스크', '목가', '작은 모임', '순수', '진보', '맑은 시냇물', '우아함', '사냥', '여린 꽃', '할미새', '이별', '위안', '슈타이어 춤', '발라드', '작은 탄식', '수다쟁이', '불안'],
+ schumann: ['멜로디', '군대 행진곡', '흥얼거리는 노래', '코랄', '작은 소품', '가엾은 고아', '사냥꾼의 노래', '거친 기수', '민요', '즐거운 농부', '시칠리아 춤곡', '무서운 아저씨'],
+};
+
+const field = (rdf, key) => rdf.match(new RegExp(`([^<]*)`))?.[1]?.trim() ?? '';
+const clamp = (value, low, high) => Math.max(low, Math.min(high, value));
+
+function addNote(track, note, time, duration, velocity) {
+ track.addNote({ midi: clamp(note, 21, 108), time, duration: Math.max(0.08, duration), velocity });
+}
+
+function onsets(notes) {
+ const grouped = new Map();
+ for (const note of notes) {
+ const key = Math.round(note.time * 50);
+ const list = grouped.get(key) ?? [];
+ list.push(note);
+ grouped.set(key, list);
+ }
+ return [...grouped.entries()].sort(([a], [b]) => a - b).map(([key, group]) => ({ time: key / 50, notes: group }));
+}
+
+const manifest = [];
+for (const item of catalog) {
+ const name = item.name;
+ const [series, numberText] = name.split('-');
+ const number = Number(numberText);
+ const source = new Midi(fs.readFileSync(path.join(sourceRoot, `${name}.mid`)));
+ const rdf = fs.readFileSync(path.join(sourceRoot, `${name}.rdf`), 'utf8');
+ const license = field(rdf, 'licence');
+ if (license !== 'Public Domain' && !/^Creative Commons Attribution-ShareAlike (2\.5|3\.0)$/.test(license)) {
+ throw new Error(`${name}: 권리 표시를 확인해야 합니다: ${license}`);
+ }
+ const nonempty = source.tracks.filter((track) => track.notes.length);
+ if (nonempty.length < 2) throw new Error(`${name}: 양손 원본이 없습니다`);
+ const rightSource = onsets(nonempty[0].notes);
+ const leftSource = onsets(nonempty.at(-1).notes);
+ const speed = Math.max(1, 58 / source.duration);
+ const introSeconds = 5.4;
+ const target = new Midi();
+ target.header.setTempo(120);
+ target.header.timeSignatures.push({ ticks: 0, timeSignature: source.header.timeSignatures[0]?.timeSignature ?? [4, 4] });
+ const right = target.addTrack();
+ right.name = 'Right Hand';
+ const left = target.addTrack();
+ left.name = 'Left Hand';
+
+ // 원곡의 첫 선율 네 음을 먼저 천천히 들려준다. 본곡과 자연스럽게 이어지는 도입부다.
+ const introPitches = rightSource.slice(0, 4).map((group) => Math.max(...group.notes.map((note) => note.midi)));
+ const firstBass = Math.min(...leftSource[0].notes.map((note) => note.midi));
+ addNote(left, firstBass, 0, 4.5, 0.48);
+ introPitches.forEach((pitch, index) => addNote(right, pitch, 0.55 + index * 1.05, 0.83, 0.55 + index * 0.04));
+
+ // 오른손은 원곡의 각 시작점에서 가장 높은 성부를 택한다. 선율의 순서와 리듬을 보존한다.
+ for (const group of rightSource) {
+ const note = group.notes.reduce((highest, current) => current.midi > highest.midi ? current : highest);
+ addNote(right, note.midi, introSeconds + group.time * speed, Math.min(2.4, note.duration * speed * 0.92), clamp(note.velocity, 0.48, 0.87));
+ }
+ // 왼손은 저음을 따라가되 가까운 시작점을 정리해 초보자가 양손을 맞출 여유를 둔다.
+ let previousBassTime = -10;
+ for (const group of leftSource) {
+ const time = introSeconds + group.time * speed;
+ if (time - previousBassTime < 0.48) continue;
+ const note = group.notes.reduce((lowest, current) => current.midi < lowest.midi ? current : lowest);
+ addNote(left, note.midi, time, clamp(note.duration * speed * 0.9, 0.38, 2.5), 0.53);
+ previousBassTime = time;
+ }
+
+ // 원곡 마지막 선율을 늦추어 다시 들려준 뒤 종지음을 충분히 유지한다.
+ const endingStart = introSeconds + source.duration * speed + 0.5;
+ const endingPitches = rightSource.slice(-3).map((group) => Math.max(...group.notes.map((note) => note.midi)));
+ const lastBass = Math.min(...leftSource.at(-1).notes.map((note) => note.midi));
+ addNote(left, lastBass, endingStart, 5.1, 0.52);
+ endingPitches.forEach((pitch, index) => addNote(right, pitch, endingStart + index * 1.05, index === 2 ? 3.0 : 0.85, 0.68 - index * 0.06));
+
+ const file = `practice-${name}.mid`;
+ const duration = new Midi(target.toArray()).duration;
+ if (duration < 60 || right.notes.length < 12 || left.notes.length < 6) throw new Error(`${name}: 연습곡 검증 실패`);
+ const notesPerSecond = right.notes.length / duration;
+ const difficulty = notesPerSecond < 2.1 ? 'beginner' : notesPerSecond < 3.5 ? 'easy' : 'intermediate';
+ fs.writeFileSync(path.join(outputRoot, file), Buffer.from(target.toArray()));
+ const title = `${series === 'burgmuller' ? '부르크뮐러 25곡' : '슈만 어린이를 위한 앨범'} ${number}번 ${names[series][number - 1]}`;
+ manifest.push({
+ storage_name: file,
+ title,
+ artist: series === 'burgmuller' ? '요한 프리드리히 부르크뮐러' : '로베르트 슈만',
+ description: `${field(rdf, 'title')} 원곡을 양손 연습용으로 단순화했습니다. 도입부와 마무리가 포함된 전체 곡입니다. 원본: Mutopia Project ${field(rdf, 'id')}. 조판: ${field(rdf, 'maintainer')}. ${license}${license === 'Public Domain' ? '' : ' 동일 조건 편곡'}.`,
+ difficulty,
+ genre: series === 'burgmuller' ? '낭만주의 연습곡' : '낭만주의 소품',
+ arrangement: 'piano_solo',
+ tags: ['양손', '도입부', '마무리', series === 'burgmuller' ? '부르크뮐러' : '슈만', difficulty === 'beginner' ? '입문' : difficulty === 'easy' ? '쉬움' : '중급', license === 'Public Domain' ? '공개 도메인' : 'CC BY-SA'],
+ source_url: item.midi_url,
+ rdf_url: item.rdf_url,
+ source_title: field(rdf, 'title'),
+ source_id: field(rdf, 'id'),
+ maintainer: field(rdf, 'maintainer'),
+ license,
+ duration_seconds: Math.round(duration * 10) / 10,
+ });
+}
+fs.writeFileSync(path.join(outputRoot, 'practice_collection.json'), `${JSON.stringify(manifest, null, 2)}\n`);
+console.log(`연습곡 ${manifest.length}개 생성. 재생 길이 ${Math.min(...manifest.map((item) => item.duration_seconds))}~${Math.max(...manifest.map((item) => item.duration_seconds))}초`);
diff --git a/scripts/check_beginner_midis.mjs b/scripts/check_beginner_midis.mjs
deleted file mode 100644
index 8a07b48..0000000
--- a/scripts/check_beginner_midis.mjs
+++ /dev/null
@@ -1,32 +0,0 @@
-import fs from "node:fs";
-import path from "node:path";
-import { fileURLToPath } from "node:url";
-import midiPackage from "../frontend/node_modules/@tonejs/midi/build/Midi.js";
-
-const { Midi } = midiPackage;
-const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "backend", "sample_midi");
-const expected = [
- ["starlight-steps-beginner.mid", 4],
- ["starlight-steps-easy.mid", 4],
- ["evening-waltz-beginner.mid", 3],
- ["sunrise-arpeggio-easy.mid", 4],
- ["black-key-pentatonic-beginner.mid", 4],
-];
-
-for (const [name, meter] of expected) {
- const midi = new Midi(fs.readFileSync(path.join(root, name)));
- const [right, left] = midi.tracks;
- if (midi.tracks.length !== 2 || !right?.notes.length || !left?.notes.length) {
- throw new Error(`${name}: 오른손과 왼손 트랙이 각각 필요합니다`);
- }
- if (midi.duration < 60 || midi.duration > 90) {
- throw new Error(`${name}: 곡 길이가 1분에서 1분 30초 사이여야 합니다 (${midi.duration.toFixed(1)}초)`);
- }
- if (midi.header.timeSignatures[0]?.timeSignature[0] !== meter) {
- throw new Error(`${name}: 박자가 일치하지 않습니다`);
- }
- if ([...right.notes, ...left.notes].some((note) => note.midi < 21 || note.midi > 108)) {
- throw new Error(`${name}: 피아노 음역을 벗어났습니다`);
- }
- console.log(`${name}: ${midi.duration.toFixed(1)}초, 오른손 ${right.notes.length}음, 왼손 ${left.notes.length}음`);
-}
diff --git a/scripts/check_practice_collection.mjs b/scripts/check_practice_collection.mjs
new file mode 100644
index 0000000..8acbf66
--- /dev/null
+++ b/scripts/check_practice_collection.mjs
@@ -0,0 +1,35 @@
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import midiPackage from '../frontend/node_modules/@tonejs/midi/build/Midi.js';
+
+const { Midi } = midiPackage;
+const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'backend', 'sample_midi');
+const collection = JSON.parse(fs.readFileSync(path.join(root, 'practice_collection.json'), 'utf8'));
+if (collection.length !== 30 || new Set(collection.map((item) => item.title)).size !== 30) {
+ throw new Error('서로 다른 제목의 연습곡 30개가 필요합니다');
+}
+const motifs = new Set();
+for (const item of collection) {
+ const midi = new Midi(fs.readFileSync(path.join(root, item.storage_name)));
+ const [right, left] = midi.tracks;
+ if (midi.tracks.length !== 2 || right.notes.length < 12 || left.notes.length < 6) {
+ throw new Error(`${item.storage_name}: 양손 음표가 부족합니다`);
+ }
+ if (midi.duration < 60 || midi.duration > 180) {
+ throw new Error(`${item.storage_name}: 곡 길이가 기준을 벗어납니다`);
+ }
+ if (right.notes[0].time < 0.4 || right.notes[4].time < 5 || left.notes[0].time !== 0) {
+ throw new Error(`${item.storage_name}: 도입부를 확인해야 합니다`);
+ }
+ if (midi.duration - right.notes.at(-1).time < 2.5 || midi.duration - left.notes.at(-1).time < 4) {
+ throw new Error(`${item.storage_name}: 마무리 지속 시간이 부족합니다`);
+ }
+ const motif = right.notes.slice(4, 20).map((note) => note.midi).join('-');
+ if (motifs.has(motif)) throw new Error(`${item.storage_name}: 다른 곡과 선율이 같습니다`);
+ motifs.add(motif);
+ if (![...right.notes, ...left.notes].every((note) => note.midi >= 21 && note.midi <= 108)) {
+ throw new Error(`${item.storage_name}: 피아노 음역을 벗어났습니다`);
+ }
+ console.log(`${item.storage_name}: ${midi.duration.toFixed(1)}초, 오른손 ${right.notes.length}음, 왼손 ${left.notes.length}음`);
+}
diff --git a/수정기록.txt b/수정기록.txt
index d796ac6..febfa7c 100644
--- a/수정기록.txt
+++ b/수정기록.txt
@@ -43,3 +43,14 @@
- 운영 백업은 /home/syskiller/piano/backups/bach-to-basics/20261009-beginner-midi 에 보관했다. 서버 시작 시 기본 공개 곡 5건이 DB에 추가됐고 회원 데이터 이전은 없었다.
- 백엔드 컨테이너만 갱신했다. HTTPS 첫 화면과 비회원 공개 목록 및 5개 파일 다운로드를 확인했다. 기존 회원 업로드 2개를 보존했고 프런트엔드는 재시작하지 않았다.
- 실제 USB MIDI 피아노를 연결한 손별 따라치기 검사는 남아 있다.
+
+2026-10-09
+
+- 선율이 비슷했던 기본 연습곡 5개를 공개 목록과 소스에서 교체했다. 회원이 업로드한 곡은 삭제하지 않았다.
+- 부르크뮐러의 서로 다른 곡 18개와 슈만 어린이를 위한 앨범의 서로 다른 곡 12개를 양손 연습용으로 편곡했다. 각 곡의 첫 선율을 이용한 도입부와 마지막 선율을 이용한 마무리를 추가했다. 곡 길이는 69~134초다.
+- 원본 MIDI와 RDF 및 곡별 권리 정보를 함께 보관했다. 슈만 MIDI의 CC BY-SA 2.5와 3.0 조건을 편곡에도 적용했다. 유튜브 쇼츠의 선율이나 MIDI를 복제하지 않았다.
+- 변경 파일은 backend/library_db.py와 backend/tests/test_library_db.py와 backend/sample_midi 및 backend/tools/practice_sources와 scripts/build_practice_collection.mjs와 scripts/check_practice_collection.mjs 및 관련 안내 문서다.
+- 새 MIDI 30개의 양손 트랙과 서로 다른 중심 선율과 도입부와 마무리와 재생 길이를 검사했다. 서버 격리 환경에서 백엔드 단위 테스트 5개를 통과했다.
+- 운영 DB와 기존 MIDI 파일과 수정 대상 소스는 /home/syskiller/piano/backups/bach-to-basics/20261009-practice-30-intro-ending 에 백업했다. 백엔드 컨테이너만 다시 빌드했다.
+- HTTPS 첫 화면과 비회원 목록에서 새 공개 곡 30개 및 각 MIDI 파일의 다운로드를 확인했다. 기존 회원 업로드 2건을 보존했다.
+- 실제 건반을 연결해 곡별 연주감과 USB MIDI 입력을 확인하는 작업은 남아 있다.