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)를 따른다. [![Bach to Basics demo](docs/demo.gif)](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 입력을 확인하는 작업은 남아 있다.