ApolloTab 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ApolloTab/__init__.py +79 -0
- ApolloTab/player.py +1113 -0
- ApolloTab/py.typed +2 -0
- apollotab-0.2.0.dist-info/METADATA +934 -0
- apollotab-0.2.0.dist-info/RECORD +8 -0
- apollotab-0.2.0.dist-info/WHEEL +5 -0
- apollotab-0.2.0.dist-info/licenses/LICENSE +373 -0
- apollotab-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,934 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ApolloTab
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Guitar Pro file parsing, rendering, and audio playback engine library
|
|
5
|
+
Author: ZhuWenqian
|
|
6
|
+
Maintainer: ZhuWenqian
|
|
7
|
+
License-Expression: MPL-2.0
|
|
8
|
+
Project-URL: Homepage, https://github.com/Zhuwenqian/ApolloTab
|
|
9
|
+
Project-URL: Documentation, https://github.com/Zhuwenqian/ApolloTab#readme
|
|
10
|
+
Project-URL: Repository, https://github.com/Zhuwenqian/ApolloTab.git
|
|
11
|
+
Project-URL: Issues, https://github.com/Zhuwenqian/ApolloTabissues
|
|
12
|
+
Keywords: guitar-pro,gtp,tab,music,midi,renderer,audio,synth,guitar
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Topic :: Multimedia :: Sound/Audio :: MIDI
|
|
23
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.8
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: pyguitarpro>=0.11
|
|
29
|
+
Requires-Dist: PyQt5>=5.15
|
|
30
|
+
Requires-Dist: pyfluidsynth>=1.4.0
|
|
31
|
+
Provides-Extra: full
|
|
32
|
+
Requires-Dist: pyguitarpro>=0.11; extra == "full"
|
|
33
|
+
Requires-Dist: PyQt5>=5.15; extra == "full"
|
|
34
|
+
Requires-Dist: pyfluidsynth>=1.4.0; extra == "full"
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
37
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
38
|
+
Requires-Dist: black>=23.0; extra == "dev"
|
|
39
|
+
Requires-Dist: isort>=5.12; extra == "dev"
|
|
40
|
+
Requires-Dist: mypy>=1.0; extra == "dev"
|
|
41
|
+
Requires-Dist: build>=0.10; extra == "dev"
|
|
42
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
43
|
+
Dynamic: license-file
|
|
44
|
+
|
|
45
|
+
# ApolloTab
|
|
46
|
+
|
|
47
|
+
[](https://www.python.org/downloads/)
|
|
48
|
+
[](https://opensource.org/licenses/MPL-2.0/)
|
|
49
|
+
[](https://pypi.org/project/ApolloTab/)
|
|
50
|
+
|
|
51
|
+
**Guitar Pro File Parsing, Rendering, and Audio Playback Engine Library**
|
|
52
|
+
|
|
53
|
+
`ApolloTab` is a fully-featured Python library for parsing, rendering, and playing Guitar Pro (.gp3/.gp4/.gp5/.gpx) tablature files.
|
|
54
|
+
|
|
55
|
+
## Features
|
|
56
|
+
|
|
57
|
+
- **File Parsing**: Full support for GP3/GP4/GP5/GPX formats — extract song info, tracks, measures, notes, technique markings
|
|
58
|
+
- **Tablature Rendering**: Render high-quality tablature images (QPixmap) using QPainter with multi-page output
|
|
59
|
+
- **Audio Playback**: Real-time MIDI synthesis engine based on FluidSynth with SoundFont support
|
|
60
|
+
- **Technique Support**: 18 playing techniques (hammer-on, pull-off, bend, slide, harmonic, vibrato, etc.)
|
|
61
|
+
- **Highly Configurable**: Fully adjustable rendering parameters (line width, spacing, colors, fonts, etc.)
|
|
62
|
+
|
|
63
|
+
## Installation
|
|
64
|
+
|
|
65
|
+
### Install from PyPI (Recommended)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install ApolloTab
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Install from Source
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://github.com/your-repo/ApolloTab.git
|
|
75
|
+
cd ApolloTab
|
|
76
|
+
|
|
77
|
+
pip install -e .
|
|
78
|
+
|
|
79
|
+
# Or install dev environment (with test tools)
|
|
80
|
+
pip install -e ".[dev]"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Quick Start
|
|
84
|
+
|
|
85
|
+
### 1. Parse a GTP File
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from ApolloTab import parse_gtp
|
|
89
|
+
|
|
90
|
+
song = parse_gtp("my_song.gp5")
|
|
91
|
+
|
|
92
|
+
print(f"Title: {song.title}")
|
|
93
|
+
print(f"Artist: {song.artist}")
|
|
94
|
+
print(f"BPM: {song.tempo}")
|
|
95
|
+
print(f"Track count: {song.track_count}")
|
|
96
|
+
|
|
97
|
+
for track in song.tracks:
|
|
98
|
+
print(f"\nTrack: {track.name}")
|
|
99
|
+
print(f"Tuning: {track.strings}")
|
|
100
|
+
print(f"Measures: {len(track.measures)}")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 2. Render Tablature Images
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from ApolloTab import render_gtp
|
|
107
|
+
|
|
108
|
+
pages = render_gtp("my_song.gp5", track_index=0)
|
|
109
|
+
|
|
110
|
+
for i, page in enumerate(pages):
|
|
111
|
+
page.save(f"output_page_{i + 1}.png")
|
|
112
|
+
print(f"Saved page {i + 1}")
|
|
113
|
+
|
|
114
|
+
# Or use TabRenderer for finer control
|
|
115
|
+
from ApolloTab import TabRenderer, RenderConfig
|
|
116
|
+
|
|
117
|
+
config = RenderConfig(
|
|
118
|
+
page_width=2480,
|
|
119
|
+
page_height=3508,
|
|
120
|
+
line_color="#000000",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
renderer = TabRenderer(config=config)
|
|
124
|
+
pages = renderer.render(song, track_index=0)
|
|
125
|
+
|
|
126
|
+
layouts = renderer.last_layouts # List[PageLayout]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 3. Audio Playback
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from ApolloTab import (
|
|
133
|
+
parse_gtp,
|
|
134
|
+
MidiConverter,
|
|
135
|
+
SynthEngine,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
song = parse_gtp("my_song.gp5")
|
|
139
|
+
|
|
140
|
+
converter = MidiConverter()
|
|
141
|
+
events = converter.convert(song, track_index=0)
|
|
142
|
+
|
|
143
|
+
engine = SynthEngine()
|
|
144
|
+
engine.initialize()
|
|
145
|
+
engine.load_soundfont()
|
|
146
|
+
engine.set_instrument(0, 27) # Electric Guitar (MIDI program 27)
|
|
147
|
+
|
|
148
|
+
engine.load_events(events, bpm=song.tempo)
|
|
149
|
+
engine.play()
|
|
150
|
+
|
|
151
|
+
import time
|
|
152
|
+
time.sleep(5)
|
|
153
|
+
engine.pause()
|
|
154
|
+
time.sleep(2)
|
|
155
|
+
engine.resume()
|
|
156
|
+
time.sleep(5)
|
|
157
|
+
|
|
158
|
+
engine.stop()
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 4. Complete Example: Parse -> Render -> Play
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from ApolloTab import (
|
|
165
|
+
parse_gtp,
|
|
166
|
+
TabRenderer,
|
|
167
|
+
MidiConverter,
|
|
168
|
+
SynthEngine,
|
|
169
|
+
RenderConfig,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def process_gtp_file(file_path: str, track_index: int = 0):
|
|
173
|
+
"""
|
|
174
|
+
Complete workflow: Parse -> Render -> Play
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
file_path: Path to .gp3/.gp4/.gp5/.gpx file
|
|
178
|
+
track_index: Track index to process (default: first track)
|
|
179
|
+
"""
|
|
180
|
+
# Step 1: Parse
|
|
181
|
+
print(f"[1/3] Parsing: {file_path}")
|
|
182
|
+
song = parse_gtp(file_path)
|
|
183
|
+
print(f" OK Title: {song.title}, BPM: {song.tempo}")
|
|
184
|
+
|
|
185
|
+
# Step 2: Render
|
|
186
|
+
print("[2/3] Rendering tablature...")
|
|
187
|
+
renderer = TabRenderer(RenderConfig())
|
|
188
|
+
pages = renderer.render(song, track_index=track_index)
|
|
189
|
+
|
|
190
|
+
for i, page in enumerate(pages):
|
|
191
|
+
output_file = f"{song.title}_track{track_index}_p{i + 1}.png"
|
|
192
|
+
page.save(output_file)
|
|
193
|
+
print(f" OK Saved: {output_file} ({page.width()}x{page.height()}px)")
|
|
194
|
+
|
|
195
|
+
# Step 3: Play
|
|
196
|
+
print("[3/3] Initializing audio...")
|
|
197
|
+
converter = MidiConverter()
|
|
198
|
+
events = converter.convert(song, track_index=track_index)
|
|
199
|
+
|
|
200
|
+
engine = SynthEngine()
|
|
201
|
+
engine.initialize()
|
|
202
|
+
engine.load_soundfont()
|
|
203
|
+
engine.set_instrument(0, 27)
|
|
204
|
+
engine.load_events(events, bpm=song.tempo)
|
|
205
|
+
|
|
206
|
+
print(" > Playing (Ctrl+C to stop)...")
|
|
207
|
+
engine.play()
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
while engine.is_playing:
|
|
211
|
+
time.sleep(0.1)
|
|
212
|
+
except KeyboardInterrupt:
|
|
213
|
+
print("\n [] Stopped")
|
|
214
|
+
engine.stop()
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
import sys
|
|
218
|
+
|
|
219
|
+
if len(sys.argv) < 2:
|
|
220
|
+
print("Usage: python example.py <file_path.gp5> [track_index]")
|
|
221
|
+
sys.exit(1)
|
|
222
|
+
|
|
223
|
+
file_path = sys.argv[1]
|
|
224
|
+
track_index = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
|
225
|
+
|
|
226
|
+
process_gtp_file(file_path, track_index)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## API Reference
|
|
230
|
+
|
|
231
|
+
### Core Functions
|
|
232
|
+
|
|
233
|
+
| Function | Description | Return Value |
|
|
234
|
+
|----------|-------------|--------------|
|
|
235
|
+
| `parse_gtp(path)` | Parse GTP file | `GTPSong` |
|
|
236
|
+
| `render_gtp(path, track_index)` | One-click render GTP file | `List[QPixmap]` |
|
|
237
|
+
|
|
238
|
+
### Main Classes
|
|
239
|
+
|
|
240
|
+
#### Data Models (`ApolloTab.models`)
|
|
241
|
+
|
|
242
|
+
| Class | Description |
|
|
243
|
+
|-------|-------------|
|
|
244
|
+
| `GTPSong` | Song object (title/artist/BPM/tracks list) |
|
|
245
|
+
| `GTPTrack` | Track object (name/tuning/measures list) |
|
|
246
|
+
| `GTPMeasure` | Measure object (time signature/repeat marks/beats list) |
|
|
247
|
+
| `GTPBeat` | Beat object (duration/dot/notes list) |
|
|
248
|
+
| `GTPNote` | Note object (fret/string/MIDI pitch/techniques) |
|
|
249
|
+
|
|
250
|
+
#### Parser (`ApolloTab.parser`)
|
|
251
|
+
|
|
252
|
+
| Class | Description |
|
|
253
|
+
|-------|-------------|
|
|
254
|
+
| `GTPParser` | GTP file parser class |
|
|
255
|
+
|
|
256
|
+
**Usage**:
|
|
257
|
+
```python
|
|
258
|
+
from ApolloTab.parser import GTPParser
|
|
259
|
+
|
|
260
|
+
parser = GTPParser()
|
|
261
|
+
song = parser.parse("song.gp5")
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### Renderer (`ApolloTab.renderer`)
|
|
265
|
+
|
|
266
|
+
| Class | Description |
|
|
267
|
+
|-------|-------------|
|
|
268
|
+
| `TabRenderer` | Tablature rendering engine |
|
|
269
|
+
| `TabLayoutEngine` | Layout calculation engine |
|
|
270
|
+
|
|
271
|
+
**Usage**:
|
|
272
|
+
```python
|
|
273
|
+
from ApolloTab.renderer import TabRenderer, RenderConfig
|
|
274
|
+
|
|
275
|
+
renderer = TabRenderer(RenderConfig())
|
|
276
|
+
pages = renderer.render(song, track_index=0)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Audio Engine (`ApolloTab.audio`)
|
|
280
|
+
|
|
281
|
+
| Class | Description |
|
|
282
|
+
|-------|-------------|
|
|
283
|
+
| `MidiConverter` | GTP data to MIDI event converter |
|
|
284
|
+
| `SynthEngine` | FluidSynth audio synthesis engine |
|
|
285
|
+
| `MidiEvent` | Single MIDI event data model |
|
|
286
|
+
|
|
287
|
+
**Usage**:
|
|
288
|
+
```python
|
|
289
|
+
from ApolloTab.audio import MidiConverter, SynthEngine
|
|
290
|
+
|
|
291
|
+
converter = MidiConverter()
|
|
292
|
+
events = converter.convert(song, track_index=0)
|
|
293
|
+
|
|
294
|
+
engine = SynthEngine()
|
|
295
|
+
engine.initialize()
|
|
296
|
+
engine.load_soundfont()
|
|
297
|
+
engine.load_events(events, bpm=120)
|
|
298
|
+
engine.play()
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
#### Utilities (`ApolloTab.utils`)
|
|
302
|
+
|
|
303
|
+
| Class / Constant | Description |
|
|
304
|
+
|------------------|-------------|
|
|
305
|
+
| `RenderConfig` | Rendering parameter configuration (all adjustable) |
|
|
306
|
+
| `TechniqueType` | Technique type enum (18 types) |
|
|
307
|
+
| `StandardTunings` | Standard tuning definitions |
|
|
308
|
+
| `NoteDuration` | Duration enum |
|
|
309
|
+
|
|
310
|
+
## Configuration
|
|
311
|
+
|
|
312
|
+
### RenderConfig Parameters
|
|
313
|
+
|
|
314
|
+
```python
|
|
315
|
+
config = RenderConfig(
|
|
316
|
+
# Page size
|
|
317
|
+
page_width=2480, # Page width(px), effect: larger = sharper but more memory
|
|
318
|
+
page_height=3508, # Page height(px), A4@300dpi standard size
|
|
319
|
+
|
|
320
|
+
# Margins
|
|
321
|
+
margin_top=80, # Top margin(px), effect: larger = content shifts down
|
|
322
|
+
margin_bottom=60, # Bottom margin(px)
|
|
323
|
+
margin_left=60, # Left margin(px)
|
|
324
|
+
margin_right=60, # Right margin(px)
|
|
325
|
+
|
|
326
|
+
# Tablature style
|
|
327
|
+
string_spacing=12, # String line spacing(px), effect: larger = wider, more readable
|
|
328
|
+
line_width=1, # Line thickness(px), effect: 0.5=thin, 2=thick
|
|
329
|
+
line_color="#333333", # Line color(hex), effect: changes overall tone
|
|
330
|
+
|
|
331
|
+
# Font settings
|
|
332
|
+
font_family="Arial", # Font family, effect: use system-supported font name
|
|
333
|
+
font_size_fret=10, # Fret number font size(px), effect: larger = clearer numbers
|
|
334
|
+
font_size_technique=9, # Technique label font size(px),
|
|
335
|
+
|
|
336
|
+
# System spacing
|
|
337
|
+
system_spacing=40, # System(row) spacing(px), effect: larger = more whitespace between rows
|
|
338
|
+
)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### SynthEngine Audio Parameters
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
engine = SynthEngine(
|
|
345
|
+
sample_rate=44100, # Sample rate(Hz), effect: 48000 = clearer but higher CPU usage
|
|
346
|
+
buffer_size=512, # Buffer size, effect: 256 = lower latency but may cause audio glitches
|
|
347
|
+
gain=0.8, # Master volume(0.0-1.0), effect: 1.0 = max volume
|
|
348
|
+
)
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Supported Techniques
|
|
352
|
+
|
|
353
|
+
| Technique | Abbreviation | Symbol Type |
|
|
354
|
+
|-----------|-------------|-------------|
|
|
355
|
+
| Hammer-On | H | Text label |
|
|
356
|
+
| Pull-Off | P | Text label |
|
|
357
|
+
| Slide Up | s/S | Line + arrow |
|
|
358
|
+
| Slide Down | S | Line + arrow |
|
|
359
|
+
| Bend | B | Arc + arrow + degree |
|
|
360
|
+
| Vibrato | ~ | Wavy line |
|
|
361
|
+
| Palm Mute | P.M. | Dashed extension |
|
|
362
|
+
| Staccato | . | Dot mark |
|
|
363
|
+
| Let Ring | Dashed extension | |
|
|
364
|
+
| Natural Harmonic N.H. | Diamond mark | |
|
|
365
|
+
| Artificial Harmonic A.H. | Diamond + text | |
|
|
366
|
+
| Tremolo Picking Trem.Pick. | Diagonal underline | |
|
|
367
|
+
| Trill | "tr" text | |
|
|
368
|
+
| Grace Note | Small note | |
|
|
369
|
+
| Accentuated | > | Symbol |
|
|
370
|
+
| Ghost Note | Parentheses | |
|
|
371
|
+
|
|
372
|
+
## Development Guide
|
|
373
|
+
|
|
374
|
+
### Project Structure
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
ApolloTab/
|
|
378
|
+
├── __init__.py # Package entry, exports core API
|
|
379
|
+
├── py.typed # PEP 561 type hint marker
|
|
380
|
+
├── parser/
|
|
381
|
+
│ ├── __init__.py
|
|
382
|
+
│ └── gtp_parser.py # PyGuitarPro -> GTPSong conversion
|
|
383
|
+
├── models/
|
|
384
|
+
│ ├── __init__.py
|
|
385
|
+
│ ├── song.py # Song model
|
|
386
|
+
│ ├── track.py # Track model
|
|
387
|
+
│ ├── measure.py # Measure model
|
|
388
|
+
│ ├── beat.py # Beat model
|
|
389
|
+
│ └── note.py # Note model
|
|
390
|
+
├── renderer/
|
|
391
|
+
│ ├── __init__.py
|
|
392
|
+
│ ├── tab_renderer.py # Tablature drawing engine
|
|
393
|
+
│ └── layout_engine.py # Coordinate layout calculation
|
|
394
|
+
├── audio/
|
|
395
|
+
│ ├── __init__.py
|
|
396
|
+
│ ├── midi_converter.py # GTP -> MIDI event conversion
|
|
397
|
+
│ └── synth_engine.py # FluidSynth synthesis engine
|
|
398
|
+
└── utils/
|
|
399
|
+
├── __init__.py
|
|
400
|
+
└── constants.py # Global constant definitions
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Local Development
|
|
404
|
+
|
|
405
|
+
```bash
|
|
406
|
+
git clone https://github.com/your-repo/ApolloTab.git
|
|
407
|
+
cd ApolloTab
|
|
408
|
+
|
|
409
|
+
python -m venv venv
|
|
410
|
+
source venv/bin/activate # Linux/Mac
|
|
411
|
+
# or .\venv\Scripts\activate # Windows
|
|
412
|
+
|
|
413
|
+
pip install -e ".[dev]"
|
|
414
|
+
|
|
415
|
+
pytest tests/ -v
|
|
416
|
+
|
|
417
|
+
black ApolloTab/
|
|
418
|
+
isort ApolloTab/
|
|
419
|
+
|
|
420
|
+
mypy ApolloTab/
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
### Publish to PyPI
|
|
424
|
+
|
|
425
|
+
```bash
|
|
426
|
+
python -m build
|
|
427
|
+
twine check dist/*
|
|
428
|
+
twine upload --repository testpypi dist/*
|
|
429
|
+
twine upload dist/*
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
## Dependencies
|
|
433
|
+
|
|
434
|
+
### Required Dependencies
|
|
435
|
+
|
|
436
|
+
| Package | Version | Purpose | License |
|
|
437
|
+
|---------|---------|---------|---------|
|
|
438
|
+
| [pyguitarpro](https://github.com/ozono/guitarpro) | >=0.11 | Guitar Pro file parsing | LGPL-2.1 |
|
|
439
|
+
| [PyQt5](https://www.riverbankcomputing.com/software/pyqt/) | >=5.15 | GUI rendering framework | GPL v3 |
|
|
440
|
+
| [pyfluidsynth](https://github.com/nwhitehead/pyfluidsynth) | >=1.4.0 | FluidSynth Python binding | LGPL-2.1 |
|
|
441
|
+
|
|
442
|
+
### Optional Dependencies
|
|
443
|
+
|
|
444
|
+
| Group | Package | Purpose |
|
|
445
|
+
|-------|---------|---------|
|
|
446
|
+
| dev | pytest, black, isort, mypy | Development and testing tools |
|
|
447
|
+
|
|
448
|
+
## License
|
|
449
|
+
|
|
450
|
+
This project is licensed under the [MIT License](LICENSE).
|
|
451
|
+
|
|
452
|
+
## Contributing
|
|
453
|
+
|
|
454
|
+
Issues and Pull Requests are welcome!
|
|
455
|
+
|
|
456
|
+
1. Fork this repository
|
|
457
|
+
2. Create a feature branch (`git checkout -b feature/AmazingFeature`)
|
|
458
|
+
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
|
459
|
+
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
|
460
|
+
5. Submit a Pull Request
|
|
461
|
+
|
|
462
|
+
## Related Projects
|
|
463
|
+
|
|
464
|
+
- **[TAB Score Viewer](https://github.com/your-repo/tab-score-viewer)** - Complete guitar tab viewer app built on ApolloTab
|
|
465
|
+
- **[pyguitarpro](https://github.com/ozono/guitarpro)** - Underlying library for Guitar Pro file parsing
|
|
466
|
+
- **[FluidSynth](https://github.com/FluidSynth/fluidsynth)** - Real-time MIDI synthesis engine
|
|
467
|
+
|
|
468
|
+
---
|
|
469
|
+
|
|
470
|
+
**Version**: v0.2.0
|
|
471
|
+
**Last Updated**: 2026-06-12
|
|
472
|
+
**Compatibility**: Windows / Linux / macOS (Python 3.8+)
|
|
473
|
+
|
|
474
|
+
---
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
# ApolloTab(中文)
|
|
479
|
+
|
|
480
|
+
[](https://www.python.org/downloads/)
|
|
481
|
+
[](https://opensource.org/licenses/MPL-2.0/)
|
|
482
|
+
[](https://pypi.org/project/ApolloTab/)
|
|
483
|
+
|
|
484
|
+
**Guitar Pro 文件解析、渲染与音频播放引擎库**
|
|
485
|
+
|
|
486
|
+
`ApolloTab` 是一个功能完整的 Python 库,用于解析、渲染和播放 Guitar Pro (.gp3/.gp4/.gp5/.gpx) 格式的吉他谱文件。
|
|
487
|
+
|
|
488
|
+
## 核心功能
|
|
489
|
+
|
|
490
|
+
- **文件解析**: 完整支持 GP3/GP4/GP5/GPX 格式,提取歌曲信息、音轨、小节、音符、技巧标记
|
|
491
|
+
- **六线谱渲染**: 使用 QPainter 将乐谱数据渲染为高质量六线谱图像(QPixmap),支持多页输出
|
|
492
|
+
- **音频播放**: 基于 FluidSynth 的 MIDI 合成引擎,支持 SoundFont 音色库实时播放
|
|
493
|
+
- **技巧支持**: 18种演奏技巧(击弦、勾弦、推弦、滑音、泛音、颤音等)
|
|
494
|
+
- **高度可配置**: 渲染参数完全可调(线宽、间距、颜色、字体等)
|
|
495
|
+
|
|
496
|
+
## 安装
|
|
497
|
+
|
|
498
|
+
### 从 PyPI 安装(推荐)
|
|
499
|
+
|
|
500
|
+
```bash
|
|
501
|
+
pip install ApolloTab
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
### 从源码安装
|
|
505
|
+
|
|
506
|
+
```bash
|
|
507
|
+
# 克隆仓库
|
|
508
|
+
git clone https://github.com/your-repo/ApolloTab.git
|
|
509
|
+
cd ApolloTab
|
|
510
|
+
|
|
511
|
+
# 安装依赖
|
|
512
|
+
pip install -e .
|
|
513
|
+
|
|
514
|
+
# 或安装开发环境(含测试工具)
|
|
515
|
+
pip install -e ".[dev]"
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
## 快速开始
|
|
519
|
+
|
|
520
|
+
### 1. 解析 GTP 文件
|
|
521
|
+
|
|
522
|
+
```python
|
|
523
|
+
from ApolloTab import parse_gtp
|
|
524
|
+
|
|
525
|
+
# 解析 Guitar Pro 文件
|
|
526
|
+
song = parse_gtp("my_song.gp5")
|
|
527
|
+
|
|
528
|
+
# 查看基本信息
|
|
529
|
+
print(f"标题: {song.title}")
|
|
530
|
+
print(f"艺术家: {song.artist}")
|
|
531
|
+
print(f"BPM: {song.tempo}")
|
|
532
|
+
print(f"音轨数: {song.track_count}")
|
|
533
|
+
|
|
534
|
+
# 遍历音轨
|
|
535
|
+
for track in song.tracks:
|
|
536
|
+
print(f"\n音轨: {track.name}")
|
|
537
|
+
print(f"调弦: {track.strings}")
|
|
538
|
+
print(f"小节数: {len(track.measures)}")
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
### 2. 渲染六线谱图像
|
|
542
|
+
|
|
543
|
+
```python
|
|
544
|
+
from ApolloTab import render_gtp
|
|
545
|
+
|
|
546
|
+
# 一键渲染(返回多页 QPixmap 列表)
|
|
547
|
+
pages = render_gtp("my_song.gp5", track_index=0)
|
|
548
|
+
|
|
549
|
+
# pages 是 List[QPixmap],每页一张图片
|
|
550
|
+
for i, page in enumerate(pages):
|
|
551
|
+
page.save(f"output_page_{i + 1}.png")
|
|
552
|
+
print(f"已保存第 {i + 1} 页")
|
|
553
|
+
|
|
554
|
+
# 或者使用 TabRenderer 类进行更精细的控制
|
|
555
|
+
from ApolloTab import TabRenderer, RenderConfig
|
|
556
|
+
|
|
557
|
+
# 自定义渲染配置
|
|
558
|
+
config = RenderConfig(
|
|
559
|
+
page_width=2480, # 页面宽度(px),调整效果: 越大越清晰但内存占用更多
|
|
560
|
+
page_height=3508, # 页面高度(px),A4@300dpi标准尺寸
|
|
561
|
+
line_color="#000000", # 弦线颜色
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
renderer = TabRenderer(config=config)
|
|
565
|
+
pages = renderer.render(song, track_index=0)
|
|
566
|
+
|
|
567
|
+
# 访问布局数据(用于播放光标等功能)
|
|
568
|
+
layouts = renderer.last_layouts # List[PageLayout]
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
### 3. 音频播放
|
|
572
|
+
|
|
573
|
+
```python
|
|
574
|
+
from ApolloTab import (
|
|
575
|
+
parse_gtp,
|
|
576
|
+
MidiConverter,
|
|
577
|
+
SynthEngine,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
# 解析文件
|
|
581
|
+
song = parse_gtp("my_song.gp5")
|
|
582
|
+
|
|
583
|
+
# 转换为 MIDI 事件序列
|
|
584
|
+
converter = MidiConverter()
|
|
585
|
+
events = converter.convert(song, track_index=0)
|
|
586
|
+
|
|
587
|
+
# 初始化音频引擎
|
|
588
|
+
engine = SynthEngine()
|
|
589
|
+
engine.initialize() # 初始化 FluidSynth 合成器
|
|
590
|
+
engine.load_soundfont() # 自动搜索并加载 SoundFont
|
|
591
|
+
engine.set_instrument(0, 27) # 设置通道0为电吉他(MIDI程序号27)
|
|
592
|
+
|
|
593
|
+
# 加载事件并播放
|
|
594
|
+
engine.load_events(events, bpm=song.tempo)
|
|
595
|
+
engine.play()
|
|
596
|
+
|
|
597
|
+
# 控制播放
|
|
598
|
+
import time
|
|
599
|
+
time.sleep(5) # 播放5秒
|
|
600
|
+
engine.pause()
|
|
601
|
+
time.sleep(2) # 暂停2秒
|
|
602
|
+
engine.resume()
|
|
603
|
+
time.sleep(5) # 继续播放5秒
|
|
604
|
+
|
|
605
|
+
# 停止并清理
|
|
606
|
+
engine.stop()
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
### 4. 完整示例:解析 → 渲染 → 播放
|
|
610
|
+
|
|
611
|
+
```python
|
|
612
|
+
from ApolloTab import (
|
|
613
|
+
parse_gtp,
|
|
614
|
+
TabRenderer,
|
|
615
|
+
MidiConverter,
|
|
616
|
+
SynthEngine,
|
|
617
|
+
RenderConfig,
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
def process_gtp_file(file_path: str, track_index: int = 0):
|
|
621
|
+
"""
|
|
622
|
+
完整处理流程:解析 → 渲染 → 播放
|
|
623
|
+
|
|
624
|
+
参数:
|
|
625
|
+
file_path: .gp3/.gp4/.gp5/.gpx 文件路径
|
|
626
|
+
track_index: 要处理的音轨索引(默认第1条)
|
|
627
|
+
"""
|
|
628
|
+
# ===== 步骤1: 解析 =====
|
|
629
|
+
print(f"[1/3] 正在解析: {file_path}")
|
|
630
|
+
song = parse_gtp(file_path)
|
|
631
|
+
print(f" ✓ 标题: {song.title}, BPM: {song.tempo}")
|
|
632
|
+
|
|
633
|
+
# ===== 步骤2: 渲染 =====
|
|
634
|
+
print("[2/3] 正在渲染六线谱...")
|
|
635
|
+
renderer = TabRenderer(RenderConfig())
|
|
636
|
+
pages = renderer.render(song, track_index=track_index)
|
|
637
|
+
|
|
638
|
+
for i, page in enumerate(pages):
|
|
639
|
+
output_file = f"{song.title}_track{track_index}_p{i + 1}.png"
|
|
640
|
+
page.save(output_file)
|
|
641
|
+
print(f" ✓ 已保存: {output_file} ({page.width()}x{page.height()}px)")
|
|
642
|
+
|
|
643
|
+
# ===== 步骤3: 播放 =====
|
|
644
|
+
print("[3/3] 正在初始化音频...")
|
|
645
|
+
converter = MidiConverter()
|
|
646
|
+
events = converter.convert(song, track_index=track_index)
|
|
647
|
+
|
|
648
|
+
engine = SynthEngine()
|
|
649
|
+
engine.initialize()
|
|
650
|
+
engine.load_soundfont()
|
|
651
|
+
engine.set_instrument(0, 27)
|
|
652
|
+
engine.load_events(events, bpm=song.tempo)
|
|
653
|
+
|
|
654
|
+
print(" ▶ 开始播放 (按 Ctrl+C 停止)...")
|
|
655
|
+
engine.play()
|
|
656
|
+
|
|
657
|
+
try:
|
|
658
|
+
while engine.is_playing:
|
|
659
|
+
time.sleep(0.1)
|
|
660
|
+
except KeyboardInterrupt:
|
|
661
|
+
print("\n ⏹ 停止播放")
|
|
662
|
+
engine.stop()
|
|
663
|
+
|
|
664
|
+
# 使用示例
|
|
665
|
+
if __name__ == "__main__":
|
|
666
|
+
import sys
|
|
667
|
+
|
|
668
|
+
if len(sys.argv) < 2:
|
|
669
|
+
print("用法: python example.py <文件路径.gp5> [音轨索引]")
|
|
670
|
+
sys.exit(1)
|
|
671
|
+
|
|
672
|
+
file_path = sys.argv[1]
|
|
673
|
+
track_index = int(sys.argv[2]) if len(sys.argv) > 2 else 0
|
|
674
|
+
|
|
675
|
+
process_gtp_file(file_path, track_index)
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
## API 参考
|
|
679
|
+
|
|
680
|
+
### 核心函数
|
|
681
|
+
|
|
682
|
+
| 函数 | 说明 | 返回值 |
|
|
683
|
+
|------|------|--------|
|
|
684
|
+
| `parse_gtp(path)` | 解析GTP文件 | `GTPSong` |
|
|
685
|
+
| `render_gtp(path, track_index)` | 一键渲染GTP文件 | `List[QPixmap]` |
|
|
686
|
+
|
|
687
|
+
### 主要类
|
|
688
|
+
|
|
689
|
+
#### 数据模型 (`ApolloTab.models`)
|
|
690
|
+
|
|
691
|
+
| 类名 | 说明 |
|
|
692
|
+
|------|------|
|
|
693
|
+
| `GTPSong` | 歌曲对象(标题/艺术家/BPM/音轨列表) |
|
|
694
|
+
| `GTPTrack` | 音轨对象(名称/调弦/小节列表) |
|
|
695
|
+
| `GTPMeasure` | 小节对象(拍号/重复记号/拍列表) |
|
|
696
|
+
| `GTPBeat` | 拍对象(时值/附点/音符列表) |
|
|
697
|
+
| `GTPNote` | 音符对象(品格/弦/MIDI音高/技巧) |
|
|
698
|
+
|
|
699
|
+
#### 解析器 (`ApolloTab.parser`)
|
|
700
|
+
|
|
701
|
+
| 类名 | 说明 |
|
|
702
|
+
|------|------|
|
|
703
|
+
| `GTPParser` | GTP文件解析器类 |
|
|
704
|
+
|
|
705
|
+
**用法**:
|
|
706
|
+
```python
|
|
707
|
+
from ApolloTab.parser import GTPParser
|
|
708
|
+
|
|
709
|
+
parser = GTPParser()
|
|
710
|
+
song = parser.parse("song.gp5")
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
#### 渲染器 (`ApolloTab.renderer`)
|
|
714
|
+
|
|
715
|
+
| 类名 | 说明 |
|
|
716
|
+
|------|------|
|
|
717
|
+
| `TabRenderer` | 六线谱渲染引擎 |
|
|
718
|
+
| `TabLayoutEngine` | 布局计算引擎 |
|
|
719
|
+
|
|
720
|
+
**用法**:
|
|
721
|
+
```python
|
|
722
|
+
from ApolloTab.renderer import TabRenderer, RenderConfig
|
|
723
|
+
|
|
724
|
+
renderer = TabRenderer(RenderConfig())
|
|
725
|
+
pages = renderer.render(song, track_index=0)
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
#### 音频引擎 (`ApolloTab.audio`)
|
|
729
|
+
|
|
730
|
+
| 类名 | 说明 |
|
|
731
|
+
|------|------|
|
|
732
|
+
| `MidiConverter` | GTP数据→MIDI事件转换器 |
|
|
733
|
+
| `SynthEngine` | FluidSynth音频合成引擎 |
|
|
734
|
+
| `MidiEvent` | 单个MIDI事件数据模型 |
|
|
735
|
+
|
|
736
|
+
**用法**:
|
|
737
|
+
```python
|
|
738
|
+
from ApolloTab.audio import MidiConverter, SynthEngine
|
|
739
|
+
|
|
740
|
+
converter = MidiConverter()
|
|
741
|
+
events = converter.convert(song, track_index=0)
|
|
742
|
+
|
|
743
|
+
engine = SynthEngine()
|
|
744
|
+
engine.initialize()
|
|
745
|
+
engine.load_soundfont()
|
|
746
|
+
engine.load_events(events, bpm=120)
|
|
747
|
+
engine.play()
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
#### 工具 (`ApolloTab.utils`)
|
|
751
|
+
|
|
752
|
+
| 类/常量 | 说明 |
|
|
753
|
+
|---------|------|
|
|
754
|
+
| `RenderConfig` | 渲染参数配置(全部可调) |
|
|
755
|
+
| `TechniqueType` | 技巧类型枚举(18种) |
|
|
756
|
+
| `StandardTunings` | 标准调弦定义 |
|
|
757
|
+
| `NoteDuration` | 时值枚举 |
|
|
758
|
+
|
|
759
|
+
## 配置说明
|
|
760
|
+
|
|
761
|
+
### RenderConfig 渲染参数
|
|
762
|
+
|
|
763
|
+
```python
|
|
764
|
+
config = RenderConfig(
|
|
765
|
+
# 页面尺寸
|
|
766
|
+
page_width=2480, # 页面宽度(px), 调整效果: A4@300dpi=2480, 屏幕显示可用1200
|
|
767
|
+
page_height=3508, # 页面高度(px), 调整效果: A4@3508, 可根据需要调整
|
|
768
|
+
|
|
769
|
+
# 边距
|
|
770
|
+
margin_top=80, # 上边距(px), 调整效果: 增大则内容下移
|
|
771
|
+
margin_bottom=60, # 下边距(px)
|
|
772
|
+
margin_left=60, # 左边距(px)
|
|
773
|
+
margin_right=60, # 右边距(px)
|
|
774
|
+
|
|
775
|
+
# 六线谱样式
|
|
776
|
+
string_spacing=12, # 弦线间距(px), 调整效果: 增大则谱子更宽更易读
|
|
777
|
+
line_width=1, # 弦线粗细(px), 调整效果: 0.5=细线, 2=粗线
|
|
778
|
+
line_color="#333333", # 弦线颜色(十六进制), 调整效果: 改变整体色调
|
|
779
|
+
|
|
780
|
+
# 字体设置
|
|
781
|
+
font_family="Arial", # 字体族, 调整效果: 使用系统支持的字体的名称
|
|
782
|
+
font_size_fret=10, # 品格数字大小(px), 调整效果: 增大则数字更清晰
|
|
783
|
+
font_size_technique=9, # 技巧标记大小(px),
|
|
784
|
+
|
|
785
|
+
# 系统间距
|
|
786
|
+
system_spacing=40, # 系统(行)间距(px), 调整效果: 增大则行间空白更多
|
|
787
|
+
)
|
|
788
|
+
```
|
|
789
|
+
|
|
790
|
+
### SynthEngine 音频参数
|
|
791
|
+
|
|
792
|
+
```python
|
|
793
|
+
engine = SynthEngine(
|
|
794
|
+
sample_rate=44100, # 采样率(Hz), 调整效果: 48000更清晰但CPU占用更高
|
|
795
|
+
buffer_size=512, # 缓冲区大小, 调整效果: 256延迟更低但可能爆音
|
|
796
|
+
gain=0.8, # 主音量(0.0-1.0), 调整效果: 1.0=最大音量
|
|
797
|
+
)
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
## 支持的演奏技巧
|
|
801
|
+
|
|
802
|
+
| 技巧 | 缩写 | 符号类型 |
|
|
803
|
+
|------|------|----------|
|
|
804
|
+
| 击弦 Hammer-On | H | 文字标签 |
|
|
805
|
+
| 勾弦 Pull-Off | P | 文字标签 |
|
|
806
|
+
| 上滑音 Slide Up | s/S | 连线+箭头 |
|
|
807
|
+
| 下滑音 Slide Down | S | 连线+箭头 |
|
|
808
|
+
| 推弦 Bend | B | 弧线+箭头+度数 |
|
|
809
|
+
| 颤音 Vibrato | ~ | 波浪线 |
|
|
810
|
+
| 闷音 Palm Mute | P.M. | 虚线延长线 |
|
|
811
|
+
| 断奏 Staccato | . | 点标记 |
|
|
812
|
+
| 延音 Let Ring | 虚线延长线 | |
|
|
813
|
+
| 自然泛音 N.H. | 菱形标记 | |
|
|
814
|
+
| 人工泛音 A.H. | 菱形+文字 | |
|
|
815
|
+
| 震音拨弦 Trem.Pick. | 斜线下划线 | |
|
|
816
|
+
| 颤音 Trill | "tr"文字 | |
|
|
817
|
+
| 装饰音 Grace Note | 小音符 | |
|
|
818
|
+
| 重音 Accentuated | > | 符号 |
|
|
819
|
+
| 幽灵音 Ghost Note | 括号包裹 | |
|
|
820
|
+
|
|
821
|
+
## 开发指南
|
|
822
|
+
|
|
823
|
+
### 项目结构
|
|
824
|
+
|
|
825
|
+
```
|
|
826
|
+
ApolloTab/
|
|
827
|
+
├── __init__.py # 包入口,导出核心API
|
|
828
|
+
├── py.typed # PEP 561 类型提示标记
|
|
829
|
+
├── parser/
|
|
830
|
+
│ ├── __init__.py
|
|
831
|
+
│ └── gtp_parser.py # PyGuitarPro → GTPSong 转换
|
|
832
|
+
├── models/
|
|
833
|
+
│ ├── __init__.py
|
|
834
|
+
│ ├── song.py # 歌曲模型
|
|
835
|
+
│ ├── track.py # 音轨模型
|
|
836
|
+
│ ├── measure.py # 小节模型
|
|
837
|
+
│ ├── beat.py # 拍模型
|
|
838
|
+
│ └── note.py # 音符模型
|
|
839
|
+
├── renderer/
|
|
840
|
+
│ ├── __init__.py
|
|
841
|
+
│ ├── tab_renderer.py # 六线谱绘制引擎
|
|
842
|
+
│ └── layout_engine.py # 坐标布局计算
|
|
843
|
+
├── audio/
|
|
844
|
+
│ ├── __init__.py
|
|
845
|
+
│ ├── midi_converter.py # GTP → MIDI事件转换
|
|
846
|
+
│ └── synth_engine.py # FluidSynth合成引擎
|
|
847
|
+
└── utils/
|
|
848
|
+
├── __init__.py
|
|
849
|
+
└── constants.py # 全局常量定义
|
|
850
|
+
```
|
|
851
|
+
|
|
852
|
+
### 本地开发
|
|
853
|
+
|
|
854
|
+
```bash
|
|
855
|
+
# 克隆仓库
|
|
856
|
+
git clone https://github.com/your-repo/ApolloTab.git
|
|
857
|
+
cd ApolloTab
|
|
858
|
+
|
|
859
|
+
# 创建虚拟环境
|
|
860
|
+
python -m venv venv
|
|
861
|
+
source venv/bin/activate # Linux/Mac
|
|
862
|
+
# 或 .\venv\Scripts\activate # Windows
|
|
863
|
+
|
|
864
|
+
# 安装开发依赖
|
|
865
|
+
pip install -e ".[dev]"
|
|
866
|
+
|
|
867
|
+
# 运行测试
|
|
868
|
+
pytest tests/ -v
|
|
869
|
+
|
|
870
|
+
# 代码格式化
|
|
871
|
+
black ApolloTab/
|
|
872
|
+
isort ApolloTab/
|
|
873
|
+
|
|
874
|
+
# 类型检查
|
|
875
|
+
mypy ApolloTab/
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
### 发布到 PyPI
|
|
879
|
+
|
|
880
|
+
```bash
|
|
881
|
+
# 1. 构建分发包
|
|
882
|
+
python -m build
|
|
883
|
+
|
|
884
|
+
# 2. 检查包内容
|
|
885
|
+
twine check dist/*
|
|
886
|
+
|
|
887
|
+
# 3. 发布到 TestPyPI(测试)
|
|
888
|
+
twine upload --repository testpypi dist/*
|
|
889
|
+
|
|
890
|
+
# 4. 发布到正式 PyPI
|
|
891
|
+
twine upload dist/*
|
|
892
|
+
```
|
|
893
|
+
|
|
894
|
+
## 依赖项
|
|
895
|
+
|
|
896
|
+
### 必需依赖
|
|
897
|
+
|
|
898
|
+
| 包名 | 版本 | 用途 | 许可证 |
|
|
899
|
+
|------|------|------|--------|
|
|
900
|
+
| [pyguitarpro](https://github.com/ozono/guitarpro) | >=0.11 | Guitar Pro文件解析 | LGPL-2.1 |
|
|
901
|
+
| [PyQt5](https://www.riverbankcomputing.com/software/pyqt/) | >=5.15 | GUI渲染框架 | GPL v3 |
|
|
902
|
+
| [pyfluidsynth](https://github.com/nwhitehead/pyfluidsynth) | >=1.4.0 | FluidSynth Python绑定 | LGPL-2.1 |
|
|
903
|
+
|
|
904
|
+
### 可选依赖
|
|
905
|
+
|
|
906
|
+
| 组名 | 包名 | 用途 |
|
|
907
|
+
|------|------|------|
|
|
908
|
+
| dev | pytest, black, isort, mypy | 开发和测试工具 |
|
|
909
|
+
|
|
910
|
+
## 许可证
|
|
911
|
+
|
|
912
|
+
本项目采用 [MIT License](LICENSE) 开源协议。
|
|
913
|
+
|
|
914
|
+
## 贡献
|
|
915
|
+
|
|
916
|
+
欢迎提交 Issue 和 Pull Request!
|
|
917
|
+
|
|
918
|
+
1. Fork 本仓库
|
|
919
|
+
2. 创建特性分支 (`git checkout -b feature/AmazingFeature`)
|
|
920
|
+
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
|
921
|
+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
922
|
+
5. 提交 Pull Request
|
|
923
|
+
|
|
924
|
+
## 相关项目
|
|
925
|
+
|
|
926
|
+
- **[TAB Score Viewer](https://github.com/your-repo/tab-score-viewer)** - 基于 ApolloTab 的完整吉他谱查看器应用
|
|
927
|
+
- **[pyguitarpro](https://github.com/ozono/guitarpro)** - Guitar Pro 文件解析底层库
|
|
928
|
+
- **[FluidSynth](https://github.com/FluidSynth/fluidsynth)** - 实时 MIDI 合成引擎
|
|
929
|
+
|
|
930
|
+
---
|
|
931
|
+
|
|
932
|
+
**版本**: v0.2.0
|
|
933
|
+
**最后更新**: 2026-06-12
|
|
934
|
+
**兼容性**: Windows / Linux / macOS (Python 3.8+)
|