audio8bit 0.0.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.
- audio8bit/__init__.py +3 -0
- audio8bit/cli.py +119 -0
- audio8bit/converter.py +1219 -0
- audio8bit-0.0.0.dist-info/METADATA +251 -0
- audio8bit-0.0.0.dist-info/RECORD +9 -0
- audio8bit-0.0.0.dist-info/WHEEL +5 -0
- audio8bit-0.0.0.dist-info/entry_points.txt +2 -0
- audio8bit-0.0.0.dist-info/licenses/LICENSE +143 -0
- audio8bit-0.0.0.dist-info/top_level.txt +1 -0
audio8bit/__init__.py
ADDED
audio8bit/cli.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""Command-line interface for audio8bit."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from audio8bit import version
|
|
7
|
+
from audio8bit.converter import (
|
|
8
|
+
DEFAULT_BITS,
|
|
9
|
+
DEFAULT_DUTY,
|
|
10
|
+
DEFAULT_METHOD,
|
|
11
|
+
DEFAULT_RATE,
|
|
12
|
+
DEFAULT_SOURCE,
|
|
13
|
+
DEFAULT_TRANSPOSE,
|
|
14
|
+
DEFAULT_VOICES,
|
|
15
|
+
METHOD_CHOICES,
|
|
16
|
+
SOURCE_CHOICES,
|
|
17
|
+
VOICES_CHOICES,
|
|
18
|
+
ConversionError,
|
|
19
|
+
convert,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_parser():
|
|
24
|
+
parser = argparse.ArgumentParser(
|
|
25
|
+
prog="audio8bit",
|
|
26
|
+
description="Turn a song into an 8-bit chiptune arrangement: take a "
|
|
27
|
+
"melody (the sung vocal or the instrumental lead) with "
|
|
28
|
+
"Demucs, extract its notes, snap them to the song's beat, "
|
|
29
|
+
"transpose into a new key and replay them with a pulse "
|
|
30
|
+
"lead, triangle bass and echo — like an 80s game console.",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"-i", "--input", required=True,
|
|
34
|
+
help="path to the input audio file (any format ffmpeg can read)",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"-o", "--output",
|
|
38
|
+
help="output path (default: 'output.<ext>' using the input's format)",
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"-f", "--format",
|
|
42
|
+
help="output format/extension, e.g. ogg, mp3, wav "
|
|
43
|
+
"(default: keep the input's format)",
|
|
44
|
+
)
|
|
45
|
+
parser.add_argument(
|
|
46
|
+
"-s", "--source", choices=SOURCE_CHOICES, default=DEFAULT_SOURCE,
|
|
47
|
+
help="which melody to follow: 'vocals' (the sung line), "
|
|
48
|
+
"'instrumental' (the backing lead, drums and bass removed), or "
|
|
49
|
+
f"'auto' (default: {DEFAULT_SOURCE})",
|
|
50
|
+
)
|
|
51
|
+
parser.add_argument(
|
|
52
|
+
"-m", "--method", choices=METHOD_CHOICES, default=DEFAULT_METHOD,
|
|
53
|
+
help="how to find the notes: 'transcribe' (polyphonic note model; best "
|
|
54
|
+
"for chords/instrumentals) or 'pitch' (lighter pYIN tracker snapped "
|
|
55
|
+
f"to the beat) (default: {DEFAULT_METHOD})",
|
|
56
|
+
)
|
|
57
|
+
parser.add_argument(
|
|
58
|
+
"-V", "--voices", choices=VOICES_CHOICES, default=DEFAULT_VOICES,
|
|
59
|
+
help="with --method transcribe: 'chords' plays every note (harmony and "
|
|
60
|
+
"bass kept) or 'lead' plays a single melody line "
|
|
61
|
+
f"(default: {DEFAULT_VOICES})",
|
|
62
|
+
)
|
|
63
|
+
parser.add_argument(
|
|
64
|
+
"--transpose", type=int, default=DEFAULT_TRANSPOSE,
|
|
65
|
+
help="shift the melody into another key by this many semitones, "
|
|
66
|
+
f"negative allowed (default: +{DEFAULT_TRANSPOSE})",
|
|
67
|
+
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
"--bits", type=int, default=DEFAULT_BITS,
|
|
70
|
+
help=f"bit depth to quantise to, 1-8 (default: {DEFAULT_BITS})",
|
|
71
|
+
)
|
|
72
|
+
parser.add_argument(
|
|
73
|
+
"--rate", type=int, default=DEFAULT_RATE,
|
|
74
|
+
help=f"output sample rate in Hz (default: {DEFAULT_RATE})",
|
|
75
|
+
)
|
|
76
|
+
parser.add_argument(
|
|
77
|
+
"--duty", type=float, default=DEFAULT_DUTY,
|
|
78
|
+
help=f"pulse-wave duty cycle, 0-1 (default: {DEFAULT_DUTY})",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"--version", action="version", version=f"audio8bit {version}",
|
|
82
|
+
)
|
|
83
|
+
return parser
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def main(argv=None):
|
|
87
|
+
parser = build_parser()
|
|
88
|
+
args = parser.parse_args(argv)
|
|
89
|
+
try:
|
|
90
|
+
output, quality_ok, report = convert(
|
|
91
|
+
args.input,
|
|
92
|
+
output_path=args.output,
|
|
93
|
+
format=args.format,
|
|
94
|
+
bits=args.bits,
|
|
95
|
+
rate=args.rate,
|
|
96
|
+
duty=args.duty,
|
|
97
|
+
transpose=args.transpose,
|
|
98
|
+
source=args.source,
|
|
99
|
+
method=args.method,
|
|
100
|
+
voices=args.voices,
|
|
101
|
+
)
|
|
102
|
+
except ConversionError as error:
|
|
103
|
+
print(f"audio8bit: {error}", file=sys.stderr)
|
|
104
|
+
return 1
|
|
105
|
+
print(f"Wrote {output}")
|
|
106
|
+
print("Quality report (anti-mush checks):")
|
|
107
|
+
for line in report:
|
|
108
|
+
print(f" {line}")
|
|
109
|
+
if not quality_ok:
|
|
110
|
+
print(
|
|
111
|
+
"audio8bit: warning: some quality checks failed - "
|
|
112
|
+
"the result may not sound musical",
|
|
113
|
+
file=sys.stderr,
|
|
114
|
+
)
|
|
115
|
+
return 0
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
sys.exit(main())
|