diff --git a/.gitignore b/.gitignore index effbf83..e788c05 100644 --- a/.gitignore +++ b/.gitignore @@ -163,4 +163,4 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ diff --git a/README.md b/README.md index 4b20f9d..47ddb1c 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,137 @@ Aces is a file format specifically designed for vocal synthesis that is simple, - Introduce piece parameters that allow for the use of curves to represent pitch and energy across the entire piece +## Quick Start + +Four steps to your first line of singing. + +### 1. Get your credentials + +Once your service is activated, your contact provides two values: `cooperator` (your caller name) +and `ace_token` (your request token). They are left blank in the sample below — fill them in and +it runs. + +### 2. Pick a singer + +Singer ids are listed in [Singer Information](/api/docs/singer_info.md). The sample uses `82`. + +### 3. Prepare an aces file + +The quickest option is `examples/xiaoxingxing_syllable.aces` — Chinese lyrics written as pinyin +syllables, so you do not have to look up phonemes. If you have a MIDI file, see +"Generating aces from MIDI" below. + +### 4. Submit and download the audio + +```python +import json, requests + +URL = "https://api-lora-us.svsbusiness.com/engine/api/engine/2b_compose" # use the address assigned to you +COOPERATOR = "" # fill in after activation +ACE_TOKEN = "" # fill in after activation + +resp = requests.post( + URL, + files=[("file", open("examples/xiaoxingxing_syllable.aces", "rb"))], + data={"cooperator": COOPERATOR, "ace_token": ACE_TOKEN, "speaker_id": "82"}, + timeout=300, +) +body = resp.json() +assert body["code"] == 200, body["error"] + +for piece in body["data"]: + audio = requests.get(piece["audio"], timeout=120).content + name = "piece_{}.{}".format(piece["sequence_index"], piece["output_format_suffix"]) + open(name, "wb").write(audio) + print(name, "starts at", piece["pst"], "seconds") +``` + +`pst` is the absolute time, in seconds on your original project timeline, of sample 0 of that +audio — place the audio at that time and it lines up with your backing track. The full interface +reference is in [api_doc](/api/docs/api_doc_en.md). + ## File Format Specification -For specifics regarding the file format please refer to [File Description](docs/aces_file_en.md). Examples illustrating various use-cases can be found in the `./examples/` directory. +For specifics regarding the file format please refer to [File Description](docs/aces_file_en.md). Every example under `./examples/` can be submitted to the synthesis API as-is: + +| File | What it demonstrates | +|-------------------------------|--------------------------------------------------------------------------| +| `xiaoxingxing_syllable.aces` | The simplest form: Chinese syllable (pinyin) input, no phonemes needed | +| `xiaoxingxing.aces` | Chinese phoneme input, with multi-segment `pitch.user` editing the melody | +| `Iwannafly.aces` | English phoneme input, including `br` (breath) and `slur` notes | +| `はるをあい cl するひと_2b.aces` | Japanese phoneme input | +| `vibrato_example.aces` | Controlled vibrato via the `user` + `delta` layers of `pitch` | +| `param_example.aces` | `energy` / `air` / `falsetto` / `tension` parameter curves | + + +## Lyrics to phonemes + +Chinese needs no extra tooling — put the pinyin (or a single Chinese character) in the note's +`syllable` field and the service converts it. **English, Japanese and Spanish must supply +`phone` themselves**, which is what `api/demo/lyrics2phone.py` is for: + +```bash +cd api/demo +pip install -r requirements.txt # English needs cmudict; Japanese and Spanish have no deps +python lyrics2phone.py en "twinkle twinkle little star" +python lyrics2phone.py jp "さくら さくら" +python lyrics2phone.py spa "camino de la luz" +``` + +The output is one phone list per note, one syllable per note, ready to drop into `notes`: + +``` +[["t","w","ih","ng"], ["k","ah","l"], ["l","ih"], ["t","ah","l"], ["s","t","aa","r"]] +``` + +Add `--aces out.aces` to emit a submittable ACES skeleton (with evenly spaced placeholder +timings and pitches — adjust them to your score). + +How each language works, and where it stops: + +| Language | Approach | Main limitation | +|----|----|----| +| English `en` | CMUdict lookup → ARPAbet → engine phones (1:1), syllabified by maximal onset | Words not in the dictionary (new coinages, proper nouns, numbers) raise an error rather than being guessed; supply them via `extra_dict` | +| Japanese `jp` | Kana → romaji syllable → the engine's own dictionary (kana table embedded, no dependency) | Kana only — annotate kanji first; the particles `は`/`へ` are read literally | +| Spanish `spa` | Orthographic rules → phones, with diphthongs decided by stress and vowel strength | Seseo is fixed (`z`/`ce`/`ci` all read as `s`); peninsular distinción and Rioplatense readings are not supported | + +> **Unknown words raise an error; nothing is guessed from spelling.** A wrong guess is hard for +> you to track down, while an error points straight at the word. Supply your own with +> `extra_dict={"word": ["phone", ...]}`. +> +> The phoneme inventory comes from the engine, and every phone the tool emits has been checked +> against it. The full list of known trade-offs is in the script's `CAVEATS` section. + +## Generating aces from MIDI + +`api/demo/midi2aces.py` converts a MIDI file with lyrics into aces and synthesizes it directly. +The repository ships `红昭愿.mid` as an example: + +```bash +cd api/demo +pip install -r requirements.txt +python midi2aces.py # put your credentials in acel_svs_example.py +``` + +What the MIDI must provide: + +- Track 0 must contain a `set_tempo` event; the script uses it to convert ticks to absolute seconds +- Lyrics go in `lyrics` meta events and **must be pinyin** (the script submits them as `syllable`, + so Chinese only for now) +- For a syllable held across several notes, write the lyric as `-` and the script emits a `slur` note +- The number of lyric events should match the number of notes; notes without a lyric fall back to `la` +- With multiple tracks the first one containing notes is used; pass + `midi2json(path, json_track_id=N)` to choose another + +The script also does three things for you: + +1. Drops notes shorter than 0.02s (too short to hold their phonemes) +2. Splits at gaps longer than 1.2s, then halves any piece still longer than 90s +3. Submits each piece and stitches the results into one wav using each piece's `pst` + +With the bundled `红昭愿.mid`: 232 notes spanning 139.3 seconds, split into 4 pieces. +Note the script sends **one request per piece**, each consuming one credit; to spend fewer +credits, submit several pieces in a single request (see section 4 of +[api_doc](/api/docs/api_doc_en.md)). ## Contributing @@ -29,4 +158,4 @@ We offer an API service for singing voice synthesis, please refer to the [api_do ## License -This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. +This project is licensed under the MIT License - see the [LICENCE](LICENCE) file for details. diff --git a/README_ZH.md b/README_ZH.md index cd0d8dc..a4bb3ff 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -16,8 +16,128 @@ Aces 是一个专为歌声合成设计的文件格式,它简单、开放、用 - 引入片段参数,允许使用曲线来表示整个片段的音高和能量 +## 快速开始 + +四步听到第一句歌声。 + +### 1. 取得凭据 + +开通服务后,对接人员会提供两个值:`cooperator`(请求方名称)与 `ace_token`(请求令牌)。 +下面示例中留空,填入即可运行。 + +### 2. 选一个歌手 + +歌手 id 见 [歌手信息列表](/api/docs/singer_info.md)。示例用 `82`。 + +### 3. 准备一个 aces 文件 + +最省事的办法是直接用 `examples/xiaoxingxing_syllable.aces` —— 中文拼音歌词,不必查音素。 +手里是 MIDI 的话见下面「从 MIDI 生成 aces」。 + +### 4. 提交并下载音频 + +```python +import json, requests + +URL = "https://api-lora-us.svsbusiness.com/engine/api/engine/2b_compose" # 请用对接时分配给您的地址 +COOPERATOR = "" # 开通后填入 +ACE_TOKEN = "" # 开通后填入 + +resp = requests.post( + URL, + files=[("file", open("examples/xiaoxingxing_syllable.aces", "rb"))], + data={"cooperator": COOPERATOR, "ace_token": ACE_TOKEN, "speaker_id": "82"}, + timeout=300, +) +body = resp.json() +assert body["code"] == 200, body["error"] + +for piece in body["data"]: + audio = requests.get(piece["audio"], timeout=120).content + name = "piece_{}.{}".format(piece["sequence_index"], piece["output_format_suffix"]) + open(name, "wb").write(audio) + print(name, "起始时间", piece["pst"], "秒") +``` + +`pst` 是这段音频第 0 个采样点在原工程时间轴上的绝对秒数——按它把音频摆到时间轴上, +就能与伴奏对齐。接口的完整说明见 [api_doc](/api/docs/api_doc.md)。 + ## 文件格式规范 -有关文件格式的具体信息,请参考 [文件描述](docs/aces_file.md)。在 `./examples/` 目录中可以找到各种用例的示例。 +有关文件格式的具体信息,请参考 [文件描述](docs/aces_file.md)。`./examples/` 目录下的示例都可以直接提交给合成接口: + +| 文件 | 演示内容 | +|-------------------------------|-------------------------------------------------| +| `xiaoxingxing_syllable.aces` | 最简形态:中文音节(拼音)输入,不必自己查音素 | +| `xiaoxingxing.aces` | 中文音素输入,并用多段 `pitch.user` 编辑整句音高 | +| `Iwannafly.aces` | 英文音素输入,含 `br`(换气)与 `slur`(延音)音符 | +| `はるをあい cl するひと_2b.aces` | 日文音素输入 | +| `vibrato_example.aces` | 用 `pitch` 的 `user` + `delta` 做可控颤音 | +| `param_example.aces` | `energy` / `air` / `falsetto` / `tension` 参数曲线 | + + +## 歌词转音素 + +中文不需要额外工具——直接在音符上写 `syllable`(拼音或单个汉字),服务端会转换。 +**英语 / 日语 / 西班牙语必须自己给 `phone`**,`api/demo/lyrics2phone.py` 就是干这个的: + +```bash +cd api/demo +pip install -r requirements.txt # 英语需要 cmudict;日语与西语无依赖 +python lyrics2phone.py en "twinkle twinkle little star" +python lyrics2phone.py jp "さくら さくら" +python lyrics2phone.py spa "camino de la luz" +``` + +输出是逐音符的音素列表,一个音节一个音符,直接填进 `notes` 即可: + +``` +[["t","w","ih","ng"], ["k","ah","l"], ["l","ih"], ["t","ah","l"], ["s","t","aa","r"]] +``` + +加 `--aces out.aces` 可以直接生成一个可提交的 ACES 骨架(时值与音高是等分占位值, +请按您的曲谱调整)。 + +各语言的做法与边界: + +| 语言 | 做法 | 主要限制 | +|----|----|----| +| 英语 `en` | CMUdict 查词 → ARPAbet → 引擎音素(1:1),按最大起首原则切音节 | 词典查不到的词(新词、专名、数字)会报错而不是猜,需自己用 `extra_dict` 补 | +| 日语 `jp` | 假名 → 罗马字音节 → 引擎自带词典(内嵌五十音表,无依赖) | 只认假名,汉字请先注音;助词 `は`/`へ` 按字面读 | +| 西语 `spa` | 正字法规则 → 音素,按重音与强弱元音判定二合元音 | 固定 seseo(`z`/`ce`/`ci` 一律读 `s`),不支持半岛 distinción 与阿根廷读法 | + +> **查不到的词一律报错,不按拼写猜。** 猜错的发音客户很难排查,报错至少能立刻定位。 +> 需要自己补词时传 `extra_dict={"word": ["音素", ...]}`。 +> +> 音素表以引擎为准,工具产出的音素已逐一核对在表内。完整的已知取舍见脚本内的 +> `CAVEATS` 说明。 + +## 从 MIDI 生成 aces + +`api/demo/midi2aces.py` 能把带歌词的 MIDI 转成 aces 并直接合成。仓库自带 `红昭愿.mid`: + +```bash +cd api/demo +pip install -r requirements.txt +python midi2aces.py # 凭据填在 acel_svs_example.py 里 +``` + +对 MIDI 的要求: + +- 轨道 0 必须含 `set_tempo` 事件,脚本据此把 tick 换算成绝对秒 +- 歌词写在 `lyrics` 元事件里,且**必须是拼音**(脚本按 `syllable` 提交,因此目前只支持中文) +- 拖腔(一个字唱过多个音)把歌词写成 `-`,脚本会转成 `slur` 音符 +- 歌词事件数量应与音符数一致;没有对应歌词的音符会退化成 `la` +- 多轨时默认取第一个含音符的轨道,需要指定用 `midi2json(path, json_track_id=N)` + +脚本还会替你做三件事: + +1. 丢掉短于 0.02s 的音符(太短装不下音素) +2. 在超过 1.2s 的空隙处切片,仍超过 90s 的片段再对半切 +3. 逐片提交,并按各片的 `pst` 拼成一个完整 wav + +以自带的 `红昭愿.mid` 为例:232 个音符、跨度 139.3 秒,切成 4 片。 +注意脚本是**一片一个请求**,各扣 1 个额度;若要省额度,可改为一次请求提交多片 +(上限见 [api_doc](/api/docs/api_doc.md) 第 4 节)。 ## 贡献 @@ -28,4 +148,4 @@ Aces 是一个专为歌声合成设计的文件格式,它简单、开放、用 ## 许可证 -该项目在 MIT 许可证下授权 - 有关详细信息,请参阅 [LICENSE](LICENSE) 文件。 \ No newline at end of file +该项目在 MIT 许可证下授权 - 有关详细信息,请参阅 [LICENCE](LICENCE) 文件。 \ No newline at end of file diff --git a/api/demo/acel_svs_example.py b/api/demo/acel_svs_example.py index 24d8815..c3f4a03 100644 --- a/api/demo/acel_svs_example.py +++ b/api/demo/acel_svs_example.py @@ -26,13 +26,9 @@ def download_and_open_audio(url): def one_piece_compose(aces_json): + # 声线混合: mel 是唯一的混合维度, 权重自动归一化。 + # 传 duration/pitch/air/falsetto/tension/energy 会返回 400, 详见 api_doc。 # mix_str = json.dumps({ - # "duration": [[82, 0.7], [1, 0.3]], - # "pitch": [[82, 0.7], [1, 0.3]], - # "air": [[82, 0.7], [1, 0.3]], - # "falsetto": [[82, 0.7], [1, 0.3]], - # "tension": [[82, 0.7], [1, 0.3]], - # "energy": [[82, 0.7], [1, 0.3]], # "mel": [[82, 0.7], [1, 0.3]], # }) diff --git a/api/demo/lyrics2phone.py b/api/demo/lyrics2phone.py new file mode 100644 index 0000000..6fda22c --- /dev/null +++ b/api/demo/lyrics2phone.py @@ -0,0 +1,1127 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""歌词 -> 音素: 把歌词转成 ACES 文件里每个音符的 `phone` 数组。 + +中文不需要这个工具 —— 直接在音符上写 `syllable`(拼音或单个汉字), 服务端会转换。 +本工具解决的是另外三种语言: 英语 / 日语 / 西班牙语, 它们必须自己给 `phone`。 + + python lyrics2phone.py en "twinkle twinkle little star" + python lyrics2phone.py jp "さくら さくら" + python lyrics2phone.py spa "camino de la luz" + python lyrics2phone.py en "let it go" --aces out.aces # 直接生成可提交的 aces + +输出是逐音符的音素列表, 一个音节一个音符: + + $ python lyrics2phone.py en "little star" + [["l", "ih"], ["t", "ah", "l"], ["s", "t", "aa", "r"]] + +把它填进 ACES 的 notes 即可(自己安排 start_time / end_time / pitch): + + {"start_time": 0.0, "end_time": 0.5, "type": "general", + "language": "en", "pitch": 62, "phone": ["l", "ih"]} + +依赖: 英语需要 `pip install cmudict`(离线词典); 日语与西语无依赖。 + +音素表以引擎为准, 每种语言各有一份合法音素集; 本工具产出的音素已逐一核对在表内。 +词典查不到的词会抛 OutOfVocabulary 而不是按拼写猜 —— 猜错的发音比报错更难排查。 +自己补词用 extra_dict={"word": ["音素", ...]}。 + +已知取舍与边界见文件末尾的 CAVEATS。 +""" + +import json +import re +import sys +import unicodedata + + +# ============================================================================ +# 英语 (English) —— cmudict -> ARPAbet -> 引擎音素 +# ============================================================================ +__all__ = [ + "lyrics_to_notes_en", "lyrics_to_words_en", "phones_to_notes", "word_phones", + "OutOfVocabulary", "VOWELS", "CONSONANTS", "PHONES", "VOCABLES", +] + +# ---------------------------------------------------------------- phone sets +# Verbatim from en_plan_20240411.json: phon_class.tail / phon_class.head. +VOWELS = frozenset(""" +aa ae ah ao aw ay eh er ey ih iy mv ngv nv ow oy uh uw +""".split()) + +CONSONANTS = frozenset(""" +b ch d dh dr dx f g hh jh k l m n ng p r s sh t th tr v w y z zh +""".split()) + +PHONES = VOWELS | CONSONANTS # == phon_id, the legal phone set + +# Syllabic nasals: used only for vowel-less interjections ("hmm" -> hh mv), +# which would otherwise be a note with no vowel and get rejected by check_vowel. +SYLLABIC_NASAL = {"m": "mv", "n": "nv", "ng": "ngv"} + +# --------------------------------------------------------------- onset table +# Legal syllable onsets, used to place intervocalic consonants (maximal onset +# principle). Harvested from every word-initial consonant cluster in cmudict, +# then restricted to native English onsets: singles (minus /ng/, which never +# starts a syllable), obstruent+{l,r}, C+w, and s-clusters. Loanword-only +# clusters (sh+l/m/n/w, "v l", "z w", "s v", "b w") and yod clusters ("k y", +# "l y", ...) are deliberately excluded: keeping them would pull the consonant +# off the previous note in words like "Ashley" or "value" ("val-ue" is the +# division wanted, not "va-lue"). /y/ alone is still a legal onset. +_ONSET_CLUSTERS = ( + # obstruent + l / r + "p l", "b l", "k l", "g l", "f l", "s l", + "p r", "b r", "t r", "d r", "k r", "g r", "f r", "th r", "sh r", + # C + w + "t w", "d w", "k w", "g w", "s w", "hh w", + # s + stop / nasal + "s p", "s t", "s k", "s m", "s n", + # s + stop + liquid + "s p l", "s p r", "s t r", "s k r", "s k l", "s k w", +) + +_ONSETS = frozenset( + [(c,) for c in CONSONANTS if c != "ng"] + # singles, minus /ng/ + [tuple(c.split()) for c in _ONSET_CLUSTERS] +) +assert all(set(o) <= CONSONANTS for o in _ONSETS) + +# ------------------------------------------------------------------ vocables +# Sung syllables cmudict either lacks or has only as a letter-name spelling +# ("ba" -> B IY EY, i.e. "bee-ay"). Values are engine phones. Extend freely. +# Others ("ooh", "ahh", "hmm", "la", "na", "yeah", "whoa", "oh") are already +# right in cmudict, including after the repeated-letter collapse below. +VOCABLES = { + "ba": ["b", "aa"], # cmudict: B IY EY ("bee-ay") + "oo": ["uw"], # cmudict: the letter O -> OW + "aa": ["aa"], # cmudict: the article "a" -> AH + "eeh": ["iy"], + "woah": ["w", "ow"], # not in cmudict at all + "whoah": ["w", "ow"], + # "rock 'n' roll": the clipped "and" is /@n/. cmudict's entry for "n" is + # the LETTER NAME (EH N, "en"), which is simply the wrong sound here. + "'n'": ["ah", "n"], + "'n": ["ah", "n"], + "n'": ["ah", "n"], + "n": ["ah", "n"], # "rock n roll" written without apostrophes +} + +# ---------------------------------------------------------------- letter runs +# A token that is one letter repeated ("aaa", "ooo", "zzz", "mmm") is a sung +# vocable, never a word -- but cmudict answers those spellings (directly, or +# after the repeated-letter collapse in _collapse) with LETTER NAMES, which is +# silently wrong in both phones and note count: +# aaa -> "triple-A" (3 notes!) www -> "double-you" (3 notes) +# kkk -> 3 notes sss -> "es-es" (2 notes) +# zzz -> "zee" nnn -> "en" rrr -> "ar" lll -> "el" uuu -> "you" +# So letter runs never reach cmudict at all. They resolve against extra_dict, +# then VOCABLES, then this table of sustained vowels. A run of a consonant that +# cannot carry a note ("zzz", "sss", "rrr", "www", "kkk") raises +# OutOfVocabulary instead of quietly singing the letter's name -- exactly like +# the vowel-less interjection "shh" already did. +_LETTER_RUN = { + "a": ["aa"], # "aaa" (same as VOCABLES["aa"]) + "e": ["iy"], # "eee" + "i": ["ay"], # "iii", as in the sung word "I" + "o": ["uw"], # "ooo" (same as VOCABLES["oo"]; write "ohh" for /ow/) + "u": ["uw"], # "uuu" + "m": ["mv"], # "mmm" -> syllabic /m/ + "n": ["nv"], # "nnn" -> syllabic /n/ +} + +_VOICELESS = frozenset("p t k f th".split()) # -s suffix -> /s/ +_SIBILANT = frozenset("s z sh zh ch jh".split()) # -s suffix -> /ih z/ + + +class OutOfVocabulary(ValueError): + """A word is not in cmudict; the caller must supply its phones.""" + + +# --------------------------------------------------------------- dictionary +_DICT = None + + +def _cmu(): + global _DICT + if _DICT is None: + try: + import cmudict + except ImportError: # pragma: no cover + raise RuntimeError("lyrics2phone (en) needs the 'cmudict' package: " + "pip install cmudict") + _DICT = cmudict.dict() + return _DICT + + +def _arpa_to_phones(arpa): + """['HH','AH0','L','OW1'] -> ['hh','ah','l','ow'] (validated).""" + out = [] + for sym in arpa: + p = re.sub(r"\d+$", "", sym).lower() + if p not in PHONES: # pragma: no cover + raise AssertionError("cmudict symbol %r maps to unknown phone %r" + % (sym, p)) + out.append(p) + return out + + +# -------------------------------------------------------------- syllabifier +def phones_to_notes(phones): + """Split a flat engine-phone list into notes, one syllable (one vowel) each. + + >>> phones_to_notes(["s", "t", "r", "eh", "ng", "th"]) + [['s', 't', 'r', 'eh', 'ng', 'th']] + + The engine only requires *at least* one vowel per note; the one-vowel-per- + note split done here is this module's own convention. + + Consonants between two vowels are divided by the maximal onset principle: + as many as can legally start a syllable go with the following vowel, the + rest close the preceding one. Word-initial and word-final consonants stay + where they are. Raises ValueError if there is no vowel to build a note on. + """ + if isinstance(phones, str): + raise ValueError("phones must be a list of engine phones, e.g. " + "['hh','ah'], not the string %r" % (phones,)) + try: + phones = list(phones) + except TypeError: + raise ValueError("phones must be an iterable of engine phones, got %s" + % type(phones).__name__) + bad = [p for p in phones if not isinstance(p, str) or p not in PHONES] + if bad: + raise ValueError("not legal en phones: %s" + % ", ".join(sorted(set(map(str, bad))))) + nuclei = [i for i, p in enumerate(phones) if p in VOWELS] + if not nuclei: + raise ValueError("no vowel in %s: cannot form a note" % (phones,)) + + cuts = [0] + for a, b in zip(nuclei, nuclei[1:]): + cluster = phones[a + 1:b] # consonants between vowels + split = len(cluster) # fallback: empty onset + for k in range(len(cluster) + 1): # longest legal onset wins + if k == len(cluster) or tuple(cluster[k:]) in _ONSETS: + split = k + break + cuts.append(a + 1 + split) + cuts.append(len(phones)) + return [phones[s:e] for s, e in zip(cuts, cuts[1:])] + + +# ------------------------------------------------------------- word lookup +def _check_extra(extra): + """Validate `extra_dict` up front. + + Without this, a typo'd phone surfaces much later as a misleading message: + extra_dict={"w": ["QQ","xx"]} used to report "has no vowel (QQ xx)" from + _fix_vowelless instead of "not a legal en phone", and a non-lowercase key + was silently ignored (it can never match, since lookups are lowercased). + """ + if extra is None: + return + if not hasattr(extra, "items"): + raise ValueError("extra_dict must be a dict of {word: [phones]}, got %s" + % type(extra).__name__) + for word, phones in extra.items(): + if not isinstance(word, str): + raise ValueError("extra_dict keys must be str, got %r" % (word,)) + if word != word.lower(): + raise ValueError("extra_dict keys must be lowercase (lookups are " + "lowercased, so %r could never match); use %r" + % (word, word.lower())) + if isinstance(phones, str) or not isinstance(phones, (list, tuple)): + raise ValueError("extra_dict[%r] must be a list of engine phones, " + "e.g. ['b','aa']; got %r" % (word, phones)) + if not phones: + raise ValueError("extra_dict[%r] is empty: give it at least one " + "phone" % (word,)) + bad = [p for p in phones if not isinstance(p, str) or p not in PHONES] + if bad: + raise ValueError("extra_dict[%r] contains things that are not legal " + "en phones: %s (legal phones are the 45 in " + "en_plan phon_id)" + % (word, ", ".join(repr(b) for b in bad))) + + +def _collapse(word): + """"oooooh" -> ["oooh"(3+ -> 2), "oh"(all runs -> 1)] candidate spellings.""" + two = re.sub(r"(.)\1{2,}", r"\1\1", word) + one = re.sub(r"(.)\1+", r"\1", word) + return [c for c in (two, one) if c != word] + + +def _letter_run(word): + """"aaa"/"zzz" -> its letter; anything else -> None (see _LETTER_RUN).""" + if len(word) >= 2 and word[0].isalpha() and word == word[0] * len(word): + return word[0] + return None + + +def _raw_lookup(word, extra): + if extra and word in extra: + return list(extra[word]) + if word in VOCABLES: + return list(VOCABLES[word]) + prons = _cmu().get(word) + if prons: + return _arpa_to_phones(prons[0]) # cmudict's primary variant + return None + + +def _run_lookup(word, extra): + """Phones for a letter run, from extra_dict / VOCABLES / _LETTER_RUN only. + + cmudict is deliberately skipped here: it answers "aaa"/"zzz"/"www" with the + letter's *name*, which is wrong in phones and in note count. + """ + if extra and word in extra: + return list(extra[word]) + if word in VOCABLES: + return list(VOCABLES[word]) + letter = _letter_run(word) + if letter in _LETTER_RUN: + return list(_LETTER_RUN[letter]) + return None + + +def _lookup(word, extra): + """The one lookup every path goes through: extra_dict / VOCABLES / cmudict, + except that a letter run ("aaa") never gets to ask cmudict.""" + if _letter_run(word): + return _run_lookup(word, extra) + return _raw_lookup(word, extra) + + +def word_phones(word, extra_dict=None): + """Engine phones for one word, or raise OutOfVocabulary.""" + if not isinstance(word, str): + raise ValueError("word must be a str, got %s" % type(word).__name__) + _check_extra(extra_dict) + w = word.lower() + + letter = _letter_run(w) + if letter is not None: # "aaa"/"zzz": cmudict would + got = _run_lookup(w, extra_dict) # answer with a letter name + if got: + return _fix_vowelless(got, word) + raise OutOfVocabulary( + "%r is a run of the letter %r: cmudict only has the letter's name " + "for it (e.g. 'zzz' -> \"zee\", 'www' -> \"double-you\"), which " + "would be sung wrong, and /%s/ alone cannot carry a note. Drop it " + "or pass phones yourself, e.g. extra_dict={%r: ['...']}" + % (word, letter, letter, w)) + + got = _lookup(w, extra_dict) + if got: + return _fix_vowelless(got, word) + + if w.endswith("in'"): # "lovin'" -> "loving", sung + got = _lookup(w[:-1] + "g", extra_dict) + if got: # with /n/, not /ng/. Tried + if got[-1] == "ng": # before the bare "lovin", + got = got[:-1] + ["n"] # which is a surname entry. + return _fix_vowelless(got, word) + + cands = [] + if w.strip("'") != w: # "'cause" -> "cause" + cands.append(w.strip("'")) + cands += _collapse(w) # "ohhhh" -> "ohh" -> "oh" + for c in cands: # _lookup(), so that a + got = _lookup(c, extra_dict) # candidate can never + if got: # collapse into a letter name + return _fix_vowelless(got, word) + + # last resort: regular -s / -'s inflection on a known base + for cut, base in (("'s", w[:-2]), ("s", w[:-1])): + if w.endswith(cut) and len(base) >= 2 and not base.endswith("s"): + got = _lookup(base, extra_dict) + if got: + tail = got[-1] + suf = (["ih", "z"] if tail in _SIBILANT else + ["s"] if tail in _VOICELESS else ["z"]) + return _fix_vowelless(got + suf, word) + + raise OutOfVocabulary( + "%r is not in cmudict; pass its phones in yourself, e.g. " + "lyrics_to_notes_en(text, extra_dict={%r: ['...']})" % (word, w)) + + +def _fix_vowelless(phones, word): + """"hmm" -> HH M -> hh mv, so the note has a nucleus.""" + if any(p in VOWELS for p in phones): + return phones + for i in range(len(phones) - 1, -1, -1): + if phones[i] in SYLLABIC_NASAL: + phones = list(phones) + phones[i] = SYLLABIC_NASAL[phones[i]] + return phones + raise OutOfVocabulary( + "%r has no vowel (%s) and cannot be sung on a note; drop it or pass " + "phones yourself" % (word, " ".join(phones))) + + +# ---------------------------------------------------------------- tokenizer +_APOSTROPHES = dict.fromkeys(map(ord, u"‘’ʼ´`"), u"'") +_DASHES = dict.fromkeys(map(ord, u"‐‑‒–—―"), u"-") +# Latin letters that NFKD does *not* decompose, so they need spelling out by +# hand. Everything else with a diacritic (é ï ñ à ...) is handled by the +# NFKD + strip-combining-marks step in _normalise(). +_LIGATURES = dict((ord(k), v) for k, v in { + u"ß": u"ss", u"æ": u"ae", u"Æ": u"AE", u"œ": u"oe", + u"Œ": u"OE", u"ø": u"o", u"Ø": u"O", u"ł": u"l", + u"Ł": u"L", u"đ": u"d", u"Đ": u"D", u"ı": u"i", +}.items()) +_TOKEN = re.compile(r"[0-9]+|'?[A-Za-z][A-Za-z'\-]*") +_WORDISH = u"[^\\W\\d_]*" # a run of letters, any script + + +def _normalise(text): + """Fold the lyrics to ASCII letters before tokenizing. + + _TOKEN only knows [A-Za-z], so without this any accented letter used to cut + the word in half *silently*: "naïve" -> "na"+"ve" -> "nah vee", "déjà vu" -> + "dee jay voo". Worse, the result depended on the caller's Unicode normal + form: NFC "café" raised OOV on "caf" while NFD "café" came out right. + + NFKD + dropping combining marks makes both forms identical ("cafe") and + keeps "naïve"/"jalapeño"/"Beyoncé" as single words. Letters that survive + the fold (Greek, Cyrillic, CJK, thorn, ...) raise OutOfVocabulary: this + module never silently drops or splits what it cannot read. + """ + if not isinstance(text, str): + raise ValueError("lyrics text must be a str, got %s" + % type(text).__name__) + text = text.translate(_APOSTROPHES).translate(_DASHES).translate(_LIGATURES) + text = unicodedata.normalize("NFKD", text) + text = u"".join(c for c in text if not unicodedata.combining(c)) + left = sorted(set(c for c in text if ord(c) > 127 and c.isalpha())) + if left: + m = re.search(_WORDISH + re.escape(left[0]) + _WORDISH, text, re.UNICODE) + raise OutOfVocabulary( + "%r contains letters this module cannot read (%s): only the Latin " + "alphabet is supported. Transliterate the word, or pass its phones " + "yourself via extra_dict / phones_to_notes()." + % (m.group(0) if m else left[0], u" ".join(left))) + return text + + +def _tokens(text): + out = [] + for m in _TOKEN.finditer(_normalise(text)): + tok = m.group(0) + if tok[0].isdigit(): + raise OutOfVocabulary( + "digits are not supported (%r): spell the number out, " + "e.g. '17' -> 'seventeen'" % tok) + out.append(tok.lower().strip("-")) + return out + + +def lyrics_to_words_en(text, extra_dict=None): + """[(word, [note_phones, ...]), ...] -- same notes as lyrics_to_notes_en, + grouped per word so the caller can align words with note ranges.""" + _check_extra(extra_dict) + out = [] + for tok in _tokens(text): + if not tok: + continue + try: # "x-ray", "t-shirt" first + out.append((tok, phones_to_notes(word_phones(tok, extra_dict)))) + continue + except OutOfVocabulary: + if "-" not in tok: + raise + for part in tok.split("-"): # then as a compound + if part: + out.append((part, phones_to_notes(word_phones(part, extra_dict)))) + return out + + +def lyrics_to_notes_en(text, extra_dict=None): + """English lyrics -> one phone list per note (one syllable each). + + >>> lyrics_to_notes_en("Hello world") + [['hh', 'ah'], ['l', 'ow'], ['w', 'er', 'l', 'd']] + + `extra_dict` maps a **lowercase** word to its engine phones, e.g. + {"spotify": ["s", "p", "aa", "t", "ah", "f", "ay"]}; it overrides cmudict + and is the escape hatch for names, slang and OOV words. Its phones are + checked against the engine's phone set on the way in. + + Anything this module cannot read -- an unknown word, a digit, a non-Latin + letter, a bad argument -- raises ValueError (OutOfVocabulary for words); + it never returns a silently wrong or silently shortened result. + """ + return [n for _, notes in lyrics_to_words_en(text, extra_dict) for n in notes] + + +# ============================================================================ +# 日语 (Japanese) —— 假名 -> 罗马字音节 -> 引擎自带词典 +# ============================================================================ +# 假名 -> plan dict 的罗马字键(单假名) +KANA_SINGLE = { + 'あ': 'a', 'い': 'i', 'う': 'u', 'え': 'e', 'お': 'o', + 'ぁ': 'a', 'ぃ': 'i', 'ぅ': 'u', 'ぇ': 'e', 'ぉ': 'o', + 'か': 'ka', 'き': 'ki', 'く': 'ku', 'け': 'ke', 'こ': 'ko', + 'ゕ': 'ka', 'ゖ': 'ke', 'が': 'ga', 'ぎ': 'gi', 'ぐ': 'gu', + 'げ': 'ge', 'ご': 'go', 'さ': 'sa', 'し': 'shi', 'す': 'su', + 'せ': 'se', 'そ': 'so', 'ざ': 'za', 'じ': 'ji', 'ず': 'zu', + 'ぜ': 'ze', 'ぞ': 'zo', 'た': 'ta', 'ち': 'chi', 'つ': 'tsu', + 'て': 'te', 'と': 'to', 'だ': 'da', 'ぢ': 'ji', 'づ': 'zu', + 'で': 'de', 'ど': 'do', 'な': 'na', 'に': 'ni', 'ぬ': 'nu', + 'ね': 'ne', 'の': 'no', 'は': 'ha', 'ひ': 'hi', 'ふ': 'fu', + 'へ': 'he', 'ほ': 'ho', 'ば': 'ba', 'び': 'bi', 'ぶ': 'bu', + 'べ': 'be', 'ぼ': 'bo', 'ぱ': 'pa', 'ぴ': 'pi', 'ぷ': 'pu', + 'ぺ': 'pe', 'ぽ': 'po', 'ま': 'ma', 'み': 'mi', 'む': 'mu', + 'め': 'me', 'も': 'mo', 'や': 'ya', 'ゆ': 'yu', 'よ': 'yo', + 'ゃ': 'ya', 'ゅ': 'yu', 'ょ': 'yo', 'ら': 'ra', 'り': 'ri', + 'る': 'ru', 'れ': 're', 'ろ': 'ro', 'わ': 'wa', 'ゎ': 'wa', + 'ゐ': 'wi', 'ゑ': 'we', 'を': 'o', 'ゔ': 'vu', +} + +# 假名 -> plan dict 的罗马字键(拗音 / 外来音, 两个假名一组, 优先匹配) +KANA_DIGRAPH = { + 'きゃ': 'kya', 'きゅ': 'kyu', 'きぇ': 'kye', 'きょ': 'kyo', + 'ぎゃ': 'gya', 'ぎゅ': 'gyu', 'ぎぇ': 'gye', 'ぎょ': 'gyo', + 'しゃ': 'sha', 'しゅ': 'shu', 'しぇ': 'she', 'しょ': 'sho', + 'じゃ': 'ja', 'じゅ': 'ju', 'じぇ': 'je', 'じょ': 'jo', + 'ちゃ': 'cha', 'ちゅ': 'chu', 'ちぇ': 'che', 'ちょ': 'cho', + 'ぢゃ': 'ja', 'ぢゅ': 'ju', 'ぢぇ': 'je', 'ぢょ': 'jo', + 'にゃ': 'nya', 'にゅ': 'nyu', 'にぇ': 'nye', 'にょ': 'nyo', + 'ひゃ': 'hya', 'ひゅ': 'hyu', 'ひぇ': 'hye', 'ひょ': 'hyo', + 'びゃ': 'bya', 'びゅ': 'byu', 'びぇ': 'bye', 'びょ': 'byo', + 'ぴゃ': 'pya', 'ぴゅ': 'pyu', 'ぴぇ': 'pye', 'ぴょ': 'pyo', + 'みゃ': 'mya', 'みゅ': 'myu', 'みぇ': 'mye', 'みょ': 'myo', + 'りゃ': 'rya', 'りゅ': 'ryu', 'りぇ': 'rye', 'りょ': 'ryo', + 'ふぁ': 'fa', 'ふぃ': 'fi', 'ふぇ': 'fe', 'ふぉ': 'fo', + 'ふゃ': 'fya', 'ふゅ': 'fyu', 'ふょ': 'fyo', 'ゔぁ': 'va', + 'ゔぃ': 'vi', 'ゔぇ': 've', 'ゔぉ': 'vo', 'ゔゃ': 'vya', + 'ゔゅ': 'vyu', 'ゔょ': 'vyo', 'てぃ': 'ti', 'てゃ': 'tya', + 'てゅ': 'tyu', 'てょ': 'tyo', 'とぅ': 'tu', 'でぃ': 'di', + 'でゃ': 'dya', 'でゅ': 'dyu', 'でょ': 'dyo', 'どぅ': 'du', + 'つぁ': 'tsa', 'つぃ': 'tsi', 'つぇ': 'tse', 'つぉ': 'tso', + 'すぃ': 'si', 'ずぃ': 'zi', 'うぃ': 'wi', 'うぇ': 'we', + 'うぉ': 'wo', 'いぇ': 'ye', 'くぁ': 'kwa', 'くぃ': 'kwi', + 'くぇ': 'kwe', 'くぉ': 'kwo', 'くゎ': 'kwa', 'ぐぁ': 'gwa', + 'ぐぃ': 'gwi', 'ぐぇ': 'gwe', 'ぐぉ': 'gwo', 'ぐゎ': 'gwa', +} + +# plan dict 的子集: 罗马字音节 -> 音素。逐条等于 plan 的 dict, 未手抄。 +SYLLABLE_PHONES = { + 'a': ['a'], 'ba': ['b', 'a'], 'be': ['b', 'e'], + 'bi': ['by', 'i'], 'bo': ['b', 'o'], 'bu': ['b', 'u'], + 'bya': ['by', 'a'], 'bye': ['by', 'e'], 'byo': ['by', 'o'], + 'byu': ['by', 'u'], 'cha': ['ch', 'a'], 'che': ['ch', 'e'], + 'chi': ['ch', 'i'], 'cho': ['ch', 'o'], 'chu': ['ch', 'u'], + 'da': ['d', 'a'], 'de': ['d', 'e'], 'di': ['dy', 'i'], + 'do': ['d', 'o'], 'du': ['d', 'u'], 'dya': ['dy', 'a'], + 'dyo': ['dy', 'o'], 'dyu': ['dy', 'u'], 'e': ['e'], + 'fa': ['f', 'a'], 'fe': ['f', 'e'], 'fi': ['fy', 'i'], + 'fo': ['f', 'o'], 'fu': ['f', 'u'], 'fya': ['fy', 'a'], + 'fyo': ['fy', 'o'], 'fyu': ['fy', 'u'], 'ga': ['g', 'a'], + 'ge': ['g', 'e'], 'gi': ['gy', 'i'], 'go': ['g', 'o'], + 'gu': ['gw', 'u'], 'gwa': ['gw', 'a'], 'gwe': ['gw', 'e'], + 'gwi': ['gw', 'i'], 'gwo': ['gw', 'o'], 'gya': ['gy', 'a'], + 'gye': ['gy', 'e'], 'gyo': ['gy', 'o'], 'gyu': ['gy', 'u'], + 'ha': ['h', 'a'], 'he': ['h', 'e'], 'hi': ['hy', 'i'], + 'ho': ['h', 'o'], 'hu': ['h', 'u'], 'hya': ['hy', 'a'], + 'hye': ['hy', 'e'], 'hyo': ['hy', 'o'], 'hyu': ['hy', 'u'], + 'i': ['i'], 'ja': ['j', 'a'], 'je': ['j', 'e'], 'ji': ['j', 'i'], + 'jo': ['j', 'o'], 'ju': ['j', 'u'], 'ka': ['k', 'a'], + 'ke': ['k', 'e'], 'ki': ['ky', 'i'], 'ko': ['k', 'o'], + 'ku': ['kw', 'u'], 'kwa': ['kw', 'a'], 'kwe': ['kw', 'e'], + 'kwi': ['kw', 'i'], 'kwo': ['kw', 'o'], 'kya': ['ky', 'a'], + 'kye': ['ky', 'e'], 'kyo': ['ky', 'o'], 'kyu': ['ky', 'u'], + 'ma': ['m', 'a'], 'me': ['m', 'e'], 'mi': ['my', 'i'], + 'mo': ['m', 'o'], 'mu': ['m', 'u'], 'mya': ['my', 'a'], + 'mye': ['my', 'e'], 'myo': ['my', 'o'], 'myu': ['my', 'u'], + 'na': ['n', 'a'], 'ne': ['n', 'e'], 'ni': ['ny', 'i'], + 'no': ['n', 'o'], 'nu': ['n', 'u'], 'nya': ['ny', 'a'], + 'nye': ['ny', 'e'], 'nyo': ['ny', 'o'], 'nyu': ['ny', 'u'], + 'o': ['o'], 'pa': ['p', 'a'], 'pe': ['p', 'e'], + 'pi': ['py', 'i'], 'po': ['p', 'o'], 'pu': ['p', 'u'], + 'pya': ['py', 'a'], 'pye': ['py', 'e'], 'pyo': ['py', 'o'], + 'pyu': ['py', 'u'], 'ra': ['r', 'a'], 're': ['r', 'e'], + 'ri': ['ry', 'i'], 'ro': ['r', 'o'], 'ru': ['r', 'u'], + 'rya': ['ry', 'a'], 'rye': ['ry', 'e'], 'ryo': ['ry', 'o'], + 'ryu': ['ry', 'u'], 'sa': ['s', 'a'], 'se': ['s', 'e'], + 'sha': ['sh', 'a'], 'she': ['sh', 'e'], 'shi': ['sh', 'i'], + 'sho': ['sh', 'o'], 'shu': ['sh', 'u'], 'si': ['s', 'i'], + 'so': ['s', 'o'], 'su': ['s', 'u'], 'ta': ['t', 'a'], + 'te': ['t', 'e'], 'ti': ['ty', 'i'], 'to': ['t', 'o'], + 'tsa': ['ts', 'a'], 'tse': ['ts', 'e'], 'tsi': ['ts', 'i'], + 'tso': ['ts', 'o'], 'tsu': ['ts', 'u'], 'tu': ['t', 'u'], + 'tya': ['ty', 'a'], 'tyo': ['ty', 'o'], 'tyu': ['ty', 'u'], + 'u': ['u'], 'va': ['v', 'a'], 've': ['v', 'e'], + 'vi': ['vy', 'i'], 'vo': ['v', 'o'], 'vu': ['v', 'u'], + 'vya': ['vy', 'a'], 'vyo': ['vy', 'o'], 'vyu': ['vy', 'u'], + 'wa': ['w', 'a'], 'we': ['w', 'e'], 'wi': ['w', 'i'], + 'wo': ['w', 'o'], 'ya': ['y', 'a'], 'ye': ['y', 'e'], + 'yo': ['y', 'o'], 'yu': ['y', 'u'], 'za': ['z', 'a'], + 'ze': ['z', 'e'], 'zi': ['z', 'i'], 'zo': ['z', 'o'], + 'zu': ['z', 'u'], +} + +# --------------------------------------------------------------------------- +# 线上先例过滤(CAVEATS 第 1 条) +# +# 在 regression_check/dataset 的 4,716 个线上 jp note 上逐音素计数, 下面这 5 个 +# 音素出现次数为 **0**: +# f = 0, ty = 0, dy = 0, v = 0, vy = 0 +# 而它们的日语传统近似音在同一语料里很常见: +# h = 109 (('h','u') 38 次)、ch = 51、j = 25、b = 56、by = 16 +# 也就是说 **ふ 在线上 100% 唱作 ['h','u'], 从没用过 ['f','u']**。 +# 因此默认把这 5 个零先例音素替换成线上有先例的读法(替换目标全部仍是 plan +# dict 里的合法音节, 且都是外来音进日语时的传统近似: ヴァイオリン→バイオリン、 +# ticket→チケット、radio→ラジオ、form→ホーム)。 +# +# 想要"字面"读法的调用方传 allow_unattested=True —— 但那条路的音色**线上无 +# 先例, 效果未经验证**。 +# +# 注意 fy 不在替换表里: fy 线上出现过 2 次(都是 ('fy','i')), 属"有先例但极 +# 罕见", 按"零次才替换"的规则保留。 +# --------------------------------------------------------------------------- +UNATTESTED_PHONES = frozenset({'f', 'ty', 'dy', 'v', 'vy'}) + +ATTESTED_SUBSTITUTE = { + # ふぁ行: f -> h (ファ→ハ, フ→ホ 型近似) + 'fa': 'ha', 'fe': 'he', 'fo': 'ho', 'fu': 'hu', + # てぃ/てゃ行: ty -> ch (ティ→チ, ticket→チケット) + 'ti': 'chi', 'tya': 'cha', 'tyo': 'cho', 'tyu': 'chu', + # でぃ/でゃ行: dy -> j (ディ→ジ, radio→ラジオ) + 'di': 'ji', 'dya': 'ja', 'dyo': 'jo', 'dyu': 'ju', + # ゔ行: v/vy -> b/by (ヴァイオリン→バイオリン) + 'va': 'ba', 've': 'be', 'vi': 'bi', 'vo': 'bo', 'vu': 'bu', + 'vya': 'bya', 'vyo': 'byo', 'vyu': 'byu', +} + +# plan 的 vowel_holding_dict: 长音 ー 该延续成哪个元音 +VOWEL_HOLDING = { + 'a': 'a', 'i': 'i', 'u': 'u', 'e': 'e', 'o': 'o', 'nv': 'nv', + 'mv': 'mv', 'ax': 'ax', 'ix': 'ix', 'ux': 'ux', 'ex': 'ex', 'ox': 'ox', +} + +SOKUON = 'っ' # 促音 +MORAIC_N = 'ん' # 撥音 +LONG_MARKS = 'ー〜~' # 长音符 / 波浪线(NFKC 会把 ~ 变成 ~) + +# 小假名单独出现时(没跟前一个假名组成 KANA_DIGRAPH)代表的元音。 +# 它自成一个 note = 一个 mora, 跟 ー 等价: ねぇ 与 ねー 输出完全相同。 +SMALL_VOWELS = {'ぁ': 'a', 'ぃ': 'i', 'ぅ': 'u', 'ぇ': 'e', 'ぉ': 'o'} + +# 歌词里常见的标点与空白, 直接跳过不发音。 +# 分隔符统一处理(修 J3): 中点 ・(U+30FB) 与 ASCII/全角连字符、各种破折号 +# 一律当作分隔符跳过, 不再"・ 静默丢弃而 - 报错"。 +# 注意长音符 ー(U+30FC) 与波浪线 〜/~ 不在这里, 它们在 LONG_MARKS 里发音。 +_SEPARATORS = ('-' # U+002D ASCII hyphen-minus(全角 - 经 NFKC 也变成它) + '‐‑‒–—―' # ‐ ‑ ‒ – — ― + '−' # − minus sign + '·‧・') # · ‧ ・ +IGNORED = set(' \t\r\n 、。,,..…‥!!??' + '「」『』()()[][]{}{}〈〉《》' + '"\'`“”‘’:;:;||//\\\**++==__') | set(_SEPARATORS) + + +def _to_hiragana(text): + """NFKC 归一 + 片假名转平假名, 让后面只用管一套表。""" + text = unicodedata.normalize('NFKC', str(text)) + # ヷヸヹヺ 不在常规片假名区间, 先展开成 ゔ + 小假名 + for src, dst in (('ヷ', 'ゔぁ'), ('ヸ', 'ゔぃ'), + ('ヹ', 'ゔぇ'), ('ヺ', 'ゔぉ')): + text = text.replace(src, dst) + out = [] + for ch in text: + # 片假名 ァ(30A1) ~ ヶ(30F6) -> 平假名; ヴ(30F4) 会落到 ゔ(3094) + out.append(chr(ord(ch) - 0x60) if 'ァ' <= ch <= 'ヶ' else ch) + return ''.join(out) + + +def _phones_for(key, allow_unattested): + """罗马字音节 -> 音素表。默认把线上零先例的音素换成有先例的读法。""" + if not allow_unattested: + key = ATTESTED_SUBSTITUTE.get(key, key) + return list(SYLLABLE_PHONES[key]) + + +def lyrics_to_notes_ja(text, allow_unattested=False): + """假名歌词 -> [[phone, ...], ...], 一个内层 list 就是一个 note 的 phone。 + + allow_unattested=False(默认): 只输出线上语料出现过的音素, ふ->['h','u']、 + ヴァ->['b','a']、ティ->['ch','i']、ディ->['j','i']。 + allow_unattested=True: 走 plan dict 的字面读法(f / v / vy / ty / dy)。 + **这些音素线上一次都没出现过, 音色效果未经验证。** + + >>> lyrics_to_notes_ja('こんにちは') + [['k', 'o'], ['nv'], ['ny', 'i'], ['ch', 'i'], ['h', 'a']] + >>> lyrics_to_notes_ja('ふ') + [['h', 'u']] + >>> lyrics_to_notes_ja('ふ', allow_unattested=True) + [['f', 'u']] + """ + kana = _to_hiragana(text) + notes = [] + i, n = 0, len(kana) + while i < n: + ch = kana[i] + if ch in IGNORED: + i += 1 + continue + if ch == SOKUON: + notes.append(['cl']) # 促音自成一个 note + i += 1 + continue + if ch == MORAIC_N: + notes.append(['nv']) # 撥音自成一个 note + i += 1 + continue + if ch in LONG_MARKS: + held = VOWEL_HOLDING.get(notes[-1][-1]) if notes else None + if held is None: + raise ValueError( + "长音符 %r (第 %d 个字符)前面必须是一个带元音的假名音节" % (ch, i + 1)) + notes.append([held]) # 长音自成一个 note, 延续前一个元音 + i += 1 + continue + key = KANA_DIGRAPH.get(kana[i:i + 2]) + if key is not None: + # 组成了外来音音节: 两个假名 = 1 mora = 1 个 note + notes.append(_phones_for(key, allow_unattested)) + i += 2 + continue + if ch in SMALL_VOWELS and notes: + # 没组成外来音音节的小假名(ねぇ / まぁ / ふぅ …): 它本身就是 + # 独立的一个 mora, 所以自成一个 note —— 与 ー 同一条路径。 + # 小元音跟前一个 note 的元音一致时按"延长"处理(ねぇ ≡ ねー), + # 不一致时(ねぁ 这类非常规写法)就照字面发那个元音。 + held = VOWEL_HOLDING.get(notes[-1][-1]) + vowel = SMALL_VOWELS[ch] + notes.append([held] if held == vowel else [vowel]) + i += 1 + continue + key = KANA_SINGLE.get(ch) + if key is None: + raise ValueError(_unsupported_message(ch, i)) + notes.append(_phones_for(key, allow_unattested)) + i += 1 + return notes + + +def _unsupported_message(ch, idx): + code = 'U+%04X' % ord(ch) + if ('一' <= ch <= '鿿' or '㐀' <= ch <= '䶿' + or ch in '々〆〻' or '豈' <= ch <= '﫿'): + return ("第 %d 个字符是汉字 %r(%s): 本工具不做汉字注音, " + "请先把歌词写成假名(如 '漢字' -> 'かんじ')" % (idx + 1, ch, code)) + if ch.isascii() and ch.isalpha(): + return ("第 %d 个字符是拉丁字母 %r: 日语通道只接受假名, " + "英文歌词请用英语音素" % (idx + 1, ch)) + if ch in 'ゝゞヽヾ': + return ("第 %d 个字符是叠字符 %r(%s): 请把它展开成实际假名" + % (idx + 1, ch, code)) + return "第 %d 个字符 %r(%s) 不是本工具支持的假名" % (idx + 1, ch, code) + + +CAVEATS = """\ +JA(日语)已知限制 —— 逐条都在 regression_check/dataset 的 4,716 个线上 jp note +上核对过。 + +1. **线上零先例音素已默认避开(默认行为, 会改变输出)。** ja plan 的 phon_id + 里有 f / ty / dy / v / vy, 但在线上语料里这 5 个音素出现次数都是 **0**; + 同期 h=109、ch=51、j=25、b=56、by=16。**ふ 在线上 100% 唱作 ['h','u'], + ['f','u'] 一次都没出现过。** 所以默认输出走线上有先例的传统近似: + ふ->['h','u'] ふぁ/ふぇ/ふぉ->['h','a']/['h','e']/['h','o'] + てぃ->['ch','i'] てゃ/てゅ/てょ->['ch','a']/['ch','u']/['ch','o'] + でぃ->['j','i'] でゃ/でゅ/でょ->['j','a']/['j','u']/['j','o'] + ゔ->['b','u'] ゔぁ/ゔぃ/ゔぇ/ゔぉ->['b','a']/['by','i']/['b','e']/['b','o'] + ゔゃ/ゔゅ/ゔょ->['by','a']/['by','u']/['by','o'] + 代价是 ファイト 会唱成 "ハイト"、パーティー 唱成 "パーチー"、ヴァイオリン + 唱成 "バイオリン" —— 这正是这些外来音进日语时的传统写法, 但**跟片假名字面 + 不一致**, 需要字面音的调用方传 `allow_unattested=True`。 +2. **`allow_unattested=True` 的读法线上无先例, 效果未经验证。** 打开开关后 + 会输出 f / ty / dy / v / vy。这些音素在 plan 的 phon_id 里合法, 引擎不会 + 报 453, 但线上 4,716 个 note 里一次都没出现过, 声学模型在这些音素上的表现 + **我们没有任何线上样本可以佐证**, 音色可能不稳定。请自行试听后再用。 +3. **fy 保留但极罕见。** ふぃ 仍输出 ['fy','i'](fy 线上出现 2 次, 都是 + ('fy','i')); ふゃ/ふゅ/ふょ 仍输出 ['fy',*]。按"零次才替换"的规则它有先例 + 所以保留, 但 2/4716 的样本量意味着效果同样谈不上验证充分。 +4. **note 数 = mora 数, 不等于假名数。** 促音 っ -> ['cl']、撥音 ん -> ['nv']、 + 长音 ー -> 延续前一元音, 三者各占一个独立 note; 而拗音/外来音两个假名 + (きゃ、てぃ、ゔぁ …)只占一个 note。所以 ちょっと = 3 个 note, + ぱーてぃー = 4 个 note。 +5. **小假名的两个方向都要注意。** + - 跟前一个假名能组成 KANA_DIGRAPH 的(てぃ、しゃ、ゔぁ …): 2 个假名 = 1 + 个 note, 因为它是 1 个 mora。 + - 组不成的(ねぇ、まぁ、ふぅ、ちぃ …): 小假名**自成一个 note**, 因为它就是 + 第 2 个 mora。ねぇ -> [['n','e'],['e']], 与 ねー 输出完全相同; + ふぅ -> [['h','u'],['u']]。 + 这不是不一致, 是按 mora 切分的必然结果; 但如果客户曲谱把 ねぇ 当作 1 个 + note, 音节数就会对不上, 需要调用方自己合并。 + 非常规写法(ねぁ 这种小元音跟前一个元音对不上的)按字面发该元音: [['n','e'],['a']]。 +6. **分隔符统一跳过, 且不会被当成长音。** ・(U+30FB)、·、‧、ASCII `-`、 + 全角 -、‐ ‑ ‒ – — ―、− 全部当分隔符静默跳过(ハーフ・タイム 与 + ハーフ-タイム 输出一致)。**如果你想要长音, 必须写 ー(U+30FC) 或 〜/~**, + 写成 `-` 会被丢掉、少一个 mora, 而且不会报错。 +7. **汉字一律报错, 不猜读音。** 汉字、々〆〻、叠字符 ゝゞヽヾ、拉丁字母都抛 + ValueError, 请调用方先把歌词转成假名。本工具没有词典, 也不做形态分析, + 所以像 は 读 wa、へ 读 e 这类**助词音变一概不做** —— こんにちは 输出的是 + ['h','a'] 而不是 ['w','a']。を 是唯一的例外, 按惯例直接映射成 ['o']。 +8. **引擎只要求每个 note 至少一个元音, 不是恰好一个。** (twob/validators.py + 的 check_vowel; 线上 spa 数据里就有 119 个 note 含 2 个以上元音。) + "一个 note 恰好一个元音"是**本工具的自我约束**, 不是引擎硬限制。后果: + 客户曲谱若把整个词放进一个 note, 本工具切出的 note 数会更多。 +9. **っ 后面不能直接跟长音符。** っー 会抛 ValueError(cl 不在 + vowel_holding_dict 里)。ん 后面可以(んー -> [['nv'],['nv']])。 + 开头就是长音符也会抛 ValueError。 +10. **输入非字符串时按 str() 处理。** lyrics_to_notes_ja(None) 会因为 'N' + 是拉丁字母而抛 ValueError(而不是 TypeError); 空串返回 []。 +""" + + +# ============================================================================ +# 西班牙语 (Spanish) —— 正字法规则 -> 引擎音素 +# ============================================================================ +# ------------------------------------------------------------------ 本工具会用到的音素 +# 元音(tail): 5 个单元音 + 上升二合元音 + 下降二合元音 + 成音节 m +_TAILS = frozenset([ + "a", "e", "i", "o", "u", + "ja", "je", "jo", "ju", "wa", "we", "wi", "wo", + "aj", "ej", "oj", "uj", "aw", "ew", "ow", + "mv", +]) + +_FRONT = frozenset("eiéí") # c / g / qu / gu 的前元音环境 +_GLIDE = {"i": "j", "u": "w"} # 滑音字母 -> 滑音符号 + +# 元音字母 -> (基本元音, 能否充当滑音)。带锐音符的 í/ú 破坏二合元音(río / país) +_VOWELS = { + "a": ("a", False), "e": ("e", False), "i": ("i", True), + "o": ("o", False), "u": ("u", True), "ü": ("u", True), + "á": ("a", False), "é": ("e", False), "í": ("i", False), + "ó": ("o", False), "ú": ("u", False), +} +# 混排他语言时的变音符号容错: 一律去符号 +_FOLD = {"à": "a", "è": "e", "ì": "i", "ò": "o", "ù": "u", + "â": "a", "ê": "e", "î": "i", "ô": "o", "û": "u", + "ä": "a", "ë": "e", "ï": "i", "ö": "o", "ü": "ü", + "ã": "a", "õ": "o", "ç": "s"} + +_CONS = { + "b": "b", "d": "d", "f": "f", "k": "k", "l": "l", "m": "m", + "n": "n", "p": "p", "s": "s", "t": "t", + "v": "b", # 西语 b / v 同音 + # seseo: z / ce / ci 一律读 s。音素表里**有** θ(tt, 按"双写 = 大写 X-SAMPA" + # 的解码规则 tt = X-SAMPA T = θ), 不用它是因为 tt 在线上 6338 个西语 note 里 + # 出现 0 次, 属于"合法但无训练先例", 与 caveat 4 同一条理由 —— 不是因为 + # 音素表缺 θ。半岛 distinción 若要做, 音素是够的, 缺的是线上先例。 + "z": "s", + "j": "x", # jugar + "ñ": "jj", # ɲ + "w": "w", # 外来词 +} +_LETTERS = frozenset(list(_VOWELS) + list(_CONS) + list("cghqrxy")) +_JOINERS = frozenset(["-", "­", "_", "·"]) # 字母间的连字符视为同一个词 +_NASALS = frozenset(["m", "n", "nn", "jj"]) +_CL_L = frozenset(["p", "b", "f", "g", "k"]) # 合法起首丛 C + l +_CL_R = frozenset(["p", "b", "f", "g", "k", "t", "d"]) # C + ɾ + + +def _words(text): + """切词。字母之间的连字符不算分词点, 这样 co-ra-zón 与 corazón 同结果。""" + text = unicodedata.normalize("NFC", text or "").lower() + text = "".join(_FOLD.get(ch, ch) for ch in text) + out, buf, n = [], [], len(text) + for i, ch in enumerate(text): + if ch in _LETTERS: + buf.append(ch) + elif ch in ("'", "’"): # pa'l -> pal + continue + elif ch in _JOINERS and buf and i + 1 < n and text[i + 1] in _LETTERS: + continue + elif buf: + out.append("".join(buf)); buf = [] + if buf: + out.append("".join(buf)) + return out + + +def _degeminate(segs): + """相邻的同一个辅音音素塌缩成一个。 + + 西语没有音位性长辅音, 双写只是正字法: 外来词 ro-ck / ja-zz / ha-ppy 以及 + 西语自身的 sc(a-scensor) / nn(i-nnato) 都不该在输出里出现叠辅音。 + 只塌缩**音素**相同的相邻辅音, 所以 acción(k + s)、carro(rr -> r)不受影响。 + 中间只隔着不发音的 h 也算相邻(h 没有音, 挡不住塌缩)。 + """ + out, last_c = [], None + for seg in segs: + if seg[0] == "C": + if seg[1] == last_c: + continue + last_c = seg[1] + elif seg[0] == "V": + last_c = None + out.append(seg) + return out + + +def _segment(word): + """词 -> [('C', 音素) | ('V', (基本元音, 能否滑音)) | ('H', None)]。 + + 'H' 是不发音的 h 留下的隔断标记: 它自己不产生音素, 只用来阻止 _nuclei + 把它两侧的元音合成二合元音(a-hu-mar 而不是 *au-mar)。 + """ + segs, i, n = [], 0, len(word) + while i < n: + c = word[i] + nx = word[i + 1] if i + 1 < n else "" + nx2 = word[i + 2] if i + 2 < n else "" + if c in _VOWELS: + segs.append(("V", _VOWELS[c])); i += 1 + elif c == "c": + if nx == "h": + segs.append(("C", "tss")); i += 2 # ch -> tʃ + else: + segs.append(("C", "s" if nx in _FRONT else "k")); i += 1 + elif c == "l" and nx == "l": + # ll 只有在起首(后面跟元音)才是 ʝ; 词尾/韵尾的 ll 只可能来自外来词 + # (roll / bill / full), 腭擦音不能作韵尾, 读 /l/。 + segs.append(("C", "jsl" if nx2 in _VOWELS else "l")); i += 2 + elif c == "r": + if nx == "r": + segs.append(("C", "r")); i += 2 # rr -> 颤音 + else: + segs.append(("C", "for")); i += 1 # 单 r 先按闪音, 后按环境改 + elif c == "q": + # que / qui 的 u 不发音; quá 之类保留 u 作滑音 + segs.append(("C", "k")); i += 2 if (nx == "u" and nx2 in _FRONT) else 1 + elif c == "g": + if nx in _FRONT: + segs.append(("C", "x")); i += 1 # gente / gitano + elif nx == "u" and nx2 in _FRONT: + segs.append(("C", "g")); i += 2 # gue / gui + else: + segs.append(("C", "g")); i += 1 # gato / güe(ü 留作滑音) + elif c == "h": + segs.append(("H", None)); i += 1 # 不发音, 但阻断二合元音 + elif c == "x": + if not segs: + segs.append(("C", "s")) # xilófono + elif nx in _VOWELS: + segs.extend([("C", "k"), ("C", "s")]) # examen -> ek-sa-men + else: + segs.append(("C", "s")) # extra -> es-tra + i += 1 + elif c == "y": + segs.append(("C", "jsl") if nx in _VOWELS # yo / mayo + else ("V", ("i", True))) # soy / muy / y + i += 1 + elif c in _CONS: + segs.append(("C", _CONS[c])); i += 1 + else: + i += 1 + return _degeminate(segs) + + +def _nuclei(run): + """一串相邻元音字母 -> 若干核, 每核恰好一个 tail(本工具的自我约束)。 + + ia/ie/ue -> ja/je/we(上升); ai/ei -> aj/ej(下降); + 三合元音拆成"滑音辅音 + 下降二合元音": buey -> w + ej。 + """ + out, i, n = [], 0, len(run) + while i < n: + base, glidecap = run[i] + glide, j = None, i + if glidecap and i + 1 < n and run[i + 1][0] != base: + glide, base, j = _GLIDE[base], run[i + 1][0], i + 1 + off = None + if j + 1 < n and run[j + 1][1] and run[j + 1][0] != base: + off, j = _GLIDE[run[j + 1][0]], j + 1 + if off and (base + off) in _TAILS: + core = [base + off] + elif off: + core = [base, off] # 兜底: 后滑音降级为辅音 + else: + core = [base] + if glide: + fused = glide + base + core = [fused] if (off is None and fused in _TAILS) else [glide] + core + out.append(core) + i = j + 1 + return out + + +def _items(word): + """把元音串折叠成核: [('C', ph) | ('N', [ph, ...])]。 + + 'H'(不发音的 h)在这里被消费掉: 它不产生任何音素, 但会把元音串截断, + 因此 a-h-u 是两个核, 不是一个 aw。 + """ + items, run = [], [] + for kind, val in _segment(word): + if kind == "V": + run.append(val) + continue + if run: + items.extend(("N", nu) for nu in _nuclei(run)); run = [] + if kind == "C": + items.append(("C", val)) + if run: + items.extend(("N", nu) for nu in _nuclei(run)) + return items + + +def _onset_len(cluster): + """辅音丛里有几个归下一音节的起首: 只有 C+l / C+ɾ 能带走两个。""" + if not cluster: + return 0 + if len(cluster) >= 2: + a, b = cluster[-2], cluster[-1] + if (b == "l" and a in _CL_L) or (b == "for" and a in _CL_R): + return 2 + return 1 + + +def _syllabify(items): + """起首最大化 + 合法辅音丛, 返回逐音节音素列表。""" + pos = [i for i, (k, _) in enumerate(items) if k == "N"] + if not pos: + return [] + sylls = [] + for k, p in enumerate(pos): + if k == 0: + onset = [v for kk, v in items[:p] if kk == "C"] + else: + cluster = [v for kk, v in items[pos[k - 1] + 1:p] if kk == "C"] + take = _onset_len(cluster) + sylls[-1].extend(cluster[:len(cluster) - take]) # 余下的做上一音节韵尾 + onset = cluster[len(cluster) - take:] if take else [] + sylls.append(list(onset) + list(items[p][1])) + sylls[-1].extend(v for kk, v in items[pos[-1] + 1:] if kk == "C") + return sylls + + +def _allophones(sylls): + """词内环境修正: b/d/g 近音化、r 颤音/闪音、n 在软腭音前。 + + 环境一律只看词内(与线上客户端一致), 不做跨词连音。 + """ + flat, owner = [], [] + for si, s in enumerate(sylls): + flat.extend(s); owner.extend([si] * len(s)) + src = list(flat) + for i, ph in enumerate(src): + left = src[i - 1] if i > 0 else None # None = 词首(等价于停顿后) + right = src[i + 1] if i + 1 < len(src) else None + if ph in ("b", "d", "g"): + # 停顿后、鼻音后(d 还有 l 后)是塞音, 其余位置是近音 β / ð / ɣ + if left is None or left in _NASALS or (ph == "d" and left == "l"): + continue + flat[i] = {"b": "bb", "d": "dd", "g": "gg"}[ph] + elif ph == "for": + # rosa / honra / alrededor / israel 用颤音, 其余(caro, tren, amor)用闪音 + if left is None or left in ("n", "l", "s"): + flat[i] = "r" + elif ph == "n" and right in ("k", "g"): + flat[i] = "nn" # cinco / tengo -> ŋ + out = [[] for _ in sylls] + for ph, si in zip(flat, owner): + if out[si] and out[si][-1] == ph: + continue # 音变(bb bb)或滑音兜底(w w)也不留叠音 + out[si].append(ph) + return out + + +def lyrics_to_notes_es(text): + """西语歌词 -> 逐音符音素; 每个内层 list 是一个音节(一个 note)。""" + notes = [] + for word in _words(text): + items = _items(word) + sylls = _syllabify(items) + if not sylls: + # 纯辅音的哼唱: mmm / hmm -> 成音节 m; 其余(sh 之类)不产生 note + cons = [v for k, v in items if k == "C"] + if cons and all(v == "m" for v in cons): + notes.append(["mv"]) + continue + notes.extend(_allophones(sylls)) + return notes + + +# ============================================================================ +# 统一入口与命令行 +# ============================================================================ + +# 对外文档里的语言代号 -> 本模块的转换函数 +_DISPATCH = { + "en": lyrics_to_notes_en, + "jp": lyrics_to_notes_ja, + "ja": lyrics_to_notes_ja, + "spa": lyrics_to_notes_es, + "es": lyrics_to_notes_es, +} + +SUPPORTED = ("en", "jp", "spa") + + +def lyrics_to_notes(language, text, **kwargs): + """按对外语言代号分发。中文请改用音符的 `syllable` 字段, 不走这里。""" + if language in ("ch", "cn", "zh"): + raise ValueError( + "Chinese does not need this tool: put the pinyin (or a single Chinese " + "character) in the note's `syllable` field and the service converts it." + ) + fn = _DISPATCH.get(language) + if fn is None: + raise ValueError("unsupported language %r; supported: %s" + % (language, ", ".join(SUPPORTED))) + return fn(text, **kwargs) + + +def notes_to_aces(notes, language, start=0.0, seconds_per_note=0.5, pitch=62): + """把音素列表铺成一个最简 ACES 骨架, 便于直接提交试听。 + + 时值与音高是等分的占位值 —— 真实工程里应当按您的曲谱来填。 + """ + out = [] + t = start + for phones in notes: + out.append({ + "start_time": round(t, 4), + "end_time": round(t + seconds_per_note, 4), + "type": "general", + "language": language, + "pitch": pitch, + "phone": list(phones), + }) + t += seconds_per_note + return {"version": 1.0, "notes": out} + + +def main(argv=None): + argv = list(sys.argv[1:] if argv is None else argv) + if len(argv) < 2 or argv[0] in ("-h", "--help"): + print(__doc__) + return 0 + language, text = argv[0], argv[1] + aces_path = None + if "--aces" in argv: + i = argv.index("--aces") + aces_path = argv[i + 1] if i + 1 < len(argv) else "out.aces" + try: + notes = lyrics_to_notes(language, text) + except ValueError as ex: + print("转换失败: %s" % ex, file=sys.stderr) + return 1 + print(json.dumps(notes, ensure_ascii=False)) + if aces_path: + with open(aces_path, "w", encoding="utf-8") as fp: + json.dump(notes_to_aces(notes, language), fp, ensure_ascii=False, indent=2) + print("已写出 %s (%d 个音符, 时值与音高是占位值, 请按您的曲谱调整)" + % (aces_path, len(notes)), file=sys.stderr) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/api/demo/midi2aces.py b/api/demo/midi2aces.py index 6d61d21..4c13ef9 100644 --- a/api/demo/midi2aces.py +++ b/api/demo/midi2aces.py @@ -121,7 +121,7 @@ def cut_aces(aces): version = aces["version"] original_note_list = aces["notes"] - MAX_LENGTH = 18 # Maximum length is 18 seconds + MAX_LENGTH = 90 # 单个 aces 文件的时长上限(见 docs/aces_file.md 5.7) CORASE_SPACE = 1.2 # 空隙大于1.2s则分割 # 先过滤一下超长的note @@ -136,20 +136,13 @@ def cut_aces(aces): def corase_cut(list_to_cut): corase_result = [] temp_list = [] - for i in range(len(list_to_cut)): - note = list_to_cut[i] - if i == 0: - temp_list.append(note) - else: - last_note = list_to_cut[i-1] - if note["start_time"] - last_note["end_time"] > CORASE_SPACE: - corase_result.append(temp_list) - temp_list = [] - if i == len(list_to_cut) - 1: - temp_list.append(note) - corase_result.append(temp_list) - else: - temp_list.append(note) + for i, note in enumerate(list_to_cut): + if i > 0 and note["start_time"] - list_to_cut[i - 1]["end_time"] > CORASE_SPACE: + corase_result.append(temp_list) + temp_list = [] + temp_list.append(note) + if temp_list: + corase_result.append(temp_list) return corase_result diff --git a/api/demo/requirements.txt b/api/demo/requirements.txt index 55d8cbe..5182a0e 100644 --- a/api/demo/requirements.txt +++ b/api/demo/requirements.txt @@ -2,3 +2,4 @@ mido requests numpy soundfile == 0.12.1 +cmudict # lyrics2phone.py 的英语转换需要(离线词典); 日语与西语无依赖 diff --git a/api/demo/test_2b_compose.py b/api/demo/test_2b_compose.py index 7af83cd..2f511a7 100644 --- a/api/demo/test_2b_compose.py +++ b/api/demo/test_2b_compose.py @@ -1,4 +1,5 @@ import json +import os import requests @@ -11,17 +12,15 @@ FLAG = "xxxx" +# 声线混合: mel 是唯一的混合维度, 权重自动归一化。 +# 传 duration/pitch/air/falsetto/tension/energy 会返回 400, 详见 api_doc 的说明。 # mix_str = json.dumps({ -# "duration": [[82, 0.7], [1, 0.3]], -# "pitch": [[82, 0.7], [1, 0.3]], -# "air": [[82, 0.7], [1, 0.3]], -# "falsetto": [[82, 0.7], [1, 0.3]], -# "tension": [[82, 0.7], [1, 0.3]], -# "energy": [[82, 0.7], [1, 0.3]], # "mel": [[82, 0.7], [1, 0.3]], # }) -file_url = "examples/Iwannafly.aces" +# 按脚本自身位置定位样例文件, 这样在任意工作目录下都能运行 +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +file_url = os.path.join(REPO_ROOT, "examples", "Iwannafly.aces") files = [('file', open(file_url, 'rb'))] ace_token = ACE_TOEKN diff --git a/api/docs/api_doc.md b/api/docs/api_doc.md index 1d7ef08..f228022 100644 --- a/api/docs/api_doc.md +++ b/api/docs/api_doc.md @@ -5,8 +5,12 @@ ### 1. 合成接口 -- 请求地址:`https://api.svsbusiness.com/engine/api/engine/2b_compose` - 请求方式:`POST` +- 请求地址(请使用对接时分配给您的那一个): + - 中国:`https://api.svsbusiness.com/engine/api/engine/2b_compose` + - 海外:`https://api-lora-us.svsbusiness.com/engine/api/engine/2b_compose` + +> 本文档描述**海外节点**的行为。中国节点所用的引擎版本请与对接人员确认。 #### 请求参数说明 @@ -17,33 +21,30 @@ | mix_info | string | 否 | 声线混合功能,可以选择不同的歌手进行声线融合,见下方说明 | | speaker_id | string | 否 | 当mix_info未设置时有效。单一合成音源,参考歌手列表和特点说明,不填默认为"1"。 | | extra | string | 否 | 用户可以填入任意字符串,该字符串会填写到返回数据结构中 | +| file | file | 是 | ACES 文件本身,multipart 字段名固定为 `file`。可重复该字段一次提交多个片段,见下方"多片提交" | -**注意**:每个请求文件对应的合成时长需小于18秒,且上传files不可超过4条 +**注意**:每个请求文件对应的合成时长需不大于90秒,且上传files不可超过3条 #### 声线混合功能说明 -通过接口,您可以通过更细致的音色混合来获得想要的声线。您可以针对一下7个维度来控制不同的混合比例: -- duration 主要控制咬字方式 -- pitch 主要控制唱法,也会影响部分咬字 -- air 控制演唱时的气息含量 -- falsetto 控制演唱时假声含量 -- tension 控制演唱时声带的紧张/松弛程度 -- energy 控制演唱时力量大小 -- mel 主要控制基础音色 +通过接口,您可以混合多位歌手的音色来获得想要的声线。混合比例由 `mel` 维度给出, +数组中每一项是 `[歌手id, 权重]`,权重会自动归一化(无需自行保证和为 1)。 + +例如,想要 70% 像歌手 82、30% 像歌手 1: -为了更直观的理解音色混合,举个例子说明,目前有两位歌手id分别为1和82,如果我们想要在以上维度30%像歌手而70%像歌手82,那么我们可以通过如下的json字符串来创造一个mix_info从而实现这个功能: ```python { - "duration": [[82, 0.7], [1, 0.3]], - "pitch": [[82, 0.7], [1, 0.3]], - "air": [[82, 0.7], [1, 0.3]], - "falsetto": [[82, 0.7], [1, 0.3]], - "tension": [[82, 0.7], [1, 0.3]], - "energy": [[82, 0.7], [1, 0.3]], - "mel": [[82, 0.7], [1, 0.3]], + "mel": [[82, 0.7], [1, 0.3]] } ``` +> `mel` 是 `mix_info` 里唯一的键,其余键一律返回 `400`。 +> +> - 只需要单一歌手时,直接用 `speaker_id` 参数即可,无需构造 `mix_info`。 +> - 声线混合调整的是**音色本身**。若想控制气息 / 假声 / 紧张度 / 力量的**强弱**, +> 那是另一件事,用 ACES 文件里 `piece_params` 的对应曲线 +> (见[请求文件说明](/docs/aces_file.md) 第 3 节)。 + #### 请求示例 @@ -51,50 +52,70 @@ import requests import json -url = "XXXXXXXXXXX" +url = "https://api-lora-us.svsbusiness.com/engine/api/engine/2b_compose" + +# 声线混合(可选)。只有 mel 维度生效, 权重自动归一化。 mix_str = json.dumps({ - "duration": [[82, 0.7], [1, 0.3]], - "pitch": [[82, 0.7], [1, 0.3]], - "air": [[82, 0.7], [1, 0.3]], - "falsetto": [[82, 0.7], [1, 0.3]], - "tension": [[82, 0.7], [1, 0.3]], - "energy": [[82, 0.7], [1, 0.3]], "mel": [[82, 0.7], [1, 0.3]], }) extra_str = json.dumps( {"request_id": "abcd1234"} ) -file_url = "/Users/root/demo/xiaoxingxing.aces" -files = [('file', open(file_url, 'rb'))] + +# 一次可提交多个片段: 重复 ('file', ...) 即可 +files = [ + ('file', open("/path/to/piece_1.aces", 'rb')), + ('file', open("/path/to/piece_2.aces", 'rb')), +] data_dict = { "ace_token": "XXXXXXXXXXXXXXXX", "cooperator": "XXXXXXXXXXXX", - "speaker_id": "3", + # 单一歌手用 speaker_id(歌手id见歌手列表); 若同时给了 mix_info, 则以 mix_info 为准 + "speaker_id": "82", "mix_info": mix_str, "extra": extra_str, } resp = requests.request("POST", url=url, files=files, data=data_dict) +print(resp.status_code, resp.text) ``` #### 响应示例 data格式说明: -| 参数名称 | 参数类型 | 参数描述 | -|-------|--------|--------------------------| -| audio | string | 返回音频地址 | -| pst | number | 返回音频的开始时间(根据文件内note时间计算) | +| 参数名称 | 参数类型 | 参数描述 | +|-----------------------|--------|--------------------------------------------| +| audio | string | 返回音频的预签名下载地址,可直接 GET,无需鉴权头 | +| pst | number | 该片音频**第 0 个采样点**在原工程时间轴上的绝对秒数(秒) | +| output_format_suffix | string | 音频文件后缀,默认 `ogg` | +| sequence_index | number | 该片对应上传文件的顺序,从 0 开始,与 `file` 的提交顺序一一对应 | + +**音频规格**:默认 Ogg/Opus 容器,**48kHz**,VBR 目标码率 64kbps(长音频实测约 70kbps)。 + +> 引擎内部按 44.1kHz 合成,但 Opus 原生只支持 48kHz,编码时会重采样到 48kHz, +> 因此下载到的文件采样率是 48000。如需 44.1kHz 素材请在您这一侧重采样。 + +**下载地址有效期 48 小时**(172800 秒),请在有效期内取回并自行存储。 +服务端**不缓存合成结果**,同样的请求重新提交会重新合成(并重新计费),音频不保证逐字节相同。 + +**多片提交**:重复 multipart 的 `file` 字段即可一次提交多个片段,返回的 `data` 数组按 +`sequence_index` 与提交顺序对应。一次请求中**任意一片失败则整个请求失败**(不计费, +已合成的片也不返回),需整体重试。 ```json { "data": [ { - "audio": "http://engine-ai.oss-cn-beijing.aliyuncs.com/svs%2Fv5%2Fprod%2Fv3%2Fcompose%2Frun_piece_v2023_1681297283190164.ogg?OSSAccessKeyId=LTAI5tF1JfTsJxdtaAb4Scdw&Expires=1681470083&Signature=Hv8tHgYELsVKRvb9n4qjI4c53P4%3D", - "pst": 1.334760032455542 + "audio": "https://./svs/v5/pro/v3/compose/run_piece_mamba_1681297283190164.ogg?", + "output_format_suffix": "ogg", + "pst": 1.334760032455542, + "sequence_index": 0 }, { - "audio": "http://engine-ai.oss-cn-beijing.aliyuncs.com/svs%2Fv5%2Fprod%2Fv3%2Fcompose%2Frun_piece_v2023_1681297283776864.ogg?OSSAccessKeyId=LTAI5tF1JfTsJxdtaAb4Scdw&Expires=1681470083&Signature=YFWau7XPHMNwF2vlC%2BVa0M%2FuNI0%3D", - "pst": 2.803809523809524 + "audio": "https://./svs/v5/pro/v3/compose/run_piece_mamba_1681297283776864.ogg?", + "output_format_suffix": "ogg", + "pst": 2.803809523809524, + "sequence_index": 1 } ], "code": 200, @@ -107,7 +128,9 @@ data格式说明: ### 2. 调用额度统计 -- 请求地址:`https://gateway.svsbusiness.com/bill/quota` +- 请求地址(与合成接口不同源,请使用与您节点对应的那一个): + - 中国:`https://gateway.svsbusiness.com/bill/quota` + - 海外:`https://gateway-us.svsbusiness.com/bill/quota` - 请求方式:`GET` #### 请求参数说明 @@ -152,6 +175,23 @@ data格式说明: | used_amount | number | 该token已经使用的额度 | | qps | number | 该token的合成每秒数量限制 | +**计费口径** + +- 一次**成功**的合成请求固定消耗 **1 个额度**,与本次提交的片数、每片时长、混合的歌手数量都无关。 +- 返回非 200 的请求**不计费**(400 / 429 / 453 / 503 均不消耗额度)。 +- `charging_strategy = 1`(按量计费):每次成功请求 `used_amount` 加 1,达到 `billing_balance` 后合成接口返回 400。 +- `charging_strategy = 2`(包时计费):不按次扣减,只校验 `charging_expire_time`,超期后返回 400。 + +> **额度是异步扣减的。** 合成请求返回 `200` 后,`used_amount` 需要几秒到十几秒才会更新。 +> 刚提交完就查额度会看到旧值,这是正常的,不代表没有计费。 + +因此降低额度消耗的做法有两层,按优先级: + +1. **先把内容装进尽量少的文件**。单文件上限是 90 秒(见"4. 合成限制条件")。 + 引擎按固定长度画布推理,合成 5 秒与合成 90 秒消耗的算力基本相同, + 人为切碎只会让同一段音频被反复推理。 +2. **再把多个文件合并到一次请求**(最多 3 个)。代价是任一片失败需要整体重试。 + ```json { "data": [ @@ -175,20 +215,26 @@ data格式说明: ### 3. 返回状态码 -| 状态码 | 说明 | -|-----|------------------------| -| 200 | 请求成功 | -| 503 | 并行请求数量超过上限 | -| 400 | 请求参数不合文档规范 | -| 429 | token不合法 | -| 402 | 合成引擎异常,多数情况为合成文件包含极端数据 | -| 453 | 服务器内部未知错误 | +| 状态码 | 说明 | +|-----|---------------------------------------------------------------| +| 200 | 请求成功 | +| 400 | 请求参数或文件内容不合文档规范;额度不足;超过 qps 限制 | +| 429 | token 不合法(无法解密) | +| 453 | 数据体检未通过(合成文件包含极端数据)或合成引擎异常 | +| 503 | 并行请求数量超过上限 | + +HTTP 状态码与响应体中的 `code` 字段始终一致。失败时 `data` 为 `null`, +具体原因在 `error` 字段中,通常包含出问题的音符时间与字段名,可据此定位。 + +> **例外**:`503` 由网关在请求进入服务之前直接拒绝,响应体不是 JSON(可能是 HTML)。 +> 客户端解析响应前请先判断 HTTP 状态码,不要假定 503 也能 `resp.json()`。 + ### 4. 合成限制条件 | 限制参数 | 数值 | 说明 | |-------------------|-----------|-------------------------------------------------------| -| 限制piece数量 | 4片 | 每次请求的piece数量不能超过限定数值 | -| 限制piece合成的长度 | 18s | 每个piece的长度不能超过限定时间 | -| 并行请求数量 | 20 | 并行请求数量超过限度后会直接返回503 | -| 每个token的qps限制 | 默认3,可联系调整 | 针对每个token限制对算力的占用 | +| 限制piece数量 | 3片 | 每次请求的piece数量不能超过限定数值 | +| 限制piece合成的长度 | 90s | 按 notes 时间跨度计算;另有音素总数 1200 的上限,见[请求文件说明](/docs/aces_file.md) 5.8 | +| 并行请求数量 | 20 | **节点级**上限(所有客户共享),超过后网关直接返回 503,需退避重试 | +| 每个token的qps限制 | 默认3,可联系调整 | 按 **60 秒滚动窗口的平均值**判定(窗口内请求数 > qps×60 即拒绝),超限返回 400 | diff --git a/api/docs/api_doc_en.md b/api/docs/api_doc_en.md index 4ba202d..edbc9a6 100644 --- a/api/docs/api_doc_en.md +++ b/api/docs/api_doc_en.md @@ -5,8 +5,13 @@ ### 1. Synthesis API -- Request URL: `https://api-lora-us.svsbusiness.com/engine/api/engine/2b_compose` - Request Method: `POST` +- Request URL (use the one assigned to you during onboarding): + - Overseas: `https://api-lora-us.svsbusiness.com/engine/api/engine/2b_compose` + - China: `https://api.svsbusiness.com/engine/api/engine/2b_compose` + +> This document describes the behaviour of the **overseas node**. For the engine version used by +> the China node, please confirm with your contact. #### Request Parameters @@ -16,59 +21,58 @@ | cooperator | string | Yes | Requester name (Contact the liaison to obtain) | | mix_info | string | No | Parameters for mixed tuning, selecting the sources you want to mix. Must be on the singer list and feature description | | speaker_id | string | No | Effective when `mix_info` is not set. Single source of synthesis, refer to the list of singers and feature description. Default is "1". | -| extra | string | 否 | Users can input any string, which will be filled into the returned data structure | +| extra | string | No | Users can input any string, which will be filled into the returned data structure | +| file | file | Yes | The ACES file itself; the multipart field name must be `file`. Repeat the field to submit several pieces in one request | -**Note**: Each request file must have a synthesis duration shorter than 18 seconds, and the number of uploaded files should not exceed 4. +**Note**: Each request file must have a synthesis duration of at most 90 seconds, and the number of uploaded files should not exceed 3. #### Voice Blending Feature Explanation -Through the interface, you can achieve the desired voice by more nuanced tone blending. You can control the different blending ratios based on the following 7 dimensions: -- duration mainly controls the articulation -- pitch mainly controls the singing style, and it also affects some aspects of articulation -- air controls the amount of breathiness in singing -- falsetto controls the amount of falsetto in singing -- tension controls the tension/relaxation of the vocal cords during singing -- energy controls the intensity of singing -- mel primarily controls the basic timbre +You can blend the timbres of several singers to obtain the voice you want. The blending ratio is +given by the `mel` dimension; each item is `[singer_id, weight]` and the weights are normalised +automatically (you do not have to make them sum to 1). + +For example, to sound 70% like singer 82 and 30% like singer 1: -To understand the tone blending more intuitively, here's an example: currently, there are two singers with IDs 1 and 82. If we want to sound 30% like singer 1 and 70% like singer 82 in the above dimensions, then we can create a mix_info with the following JSON string to achieve this function: ```python { - "duration": [[82, 0.7], [1, 0.3]], - "pitch": [[82, 0.7], [1, 0.3]], - "air": [[82, 0.7], [1, 0.3]], - "falsetto": [[82, 0.7], [1, 0.3]], - "tension": [[82, 0.7], [1, 0.3]], - "energy": [[82, 0.7], [1, 0.3]], - "mel": [[82, 0.7], [1, 0.3]], + "mel": [[82, 0.7], [1, 0.3]] } ``` +> `mel` is the only key in `mix_info`; any other key returns `400`. +> +> - If you only need a single singer, just use the `speaker_id` parameter — no `mix_info` needed. +> - Voice blending changes the **timbre itself**. Controlling **how much** breath / falsetto / +> tension / intensity the voice has is a separate matter — use the corresponding curves in +> `piece_params` inside the ACES file (see +> [ACES file specification](/docs/aces_file_en.md), section 3). + #### Request Example ```python import requests import json -url = "XXXXXXXXXXX" +url = "https://api-lora-us.svsbusiness.com/engine/api/engine/2b_compose" + +# Voice blending (optional). Only the mel dimension takes effect; weights are normalised. mix_str = json.dumps({ - "duration": [[82, 0.7], [1, 0.3]], - "pitch": [[82, 0.7], [1, 0.3]], - "air": [[82, 0.7], [1, 0.3]], - "falsetto": [[82, 0.7], [1, 0.3]], - "tension": [[82, 0.7], [1, 0.3]], - "energy": [[82, 0.7], [1, 0.3]], "mel": [[82, 0.7], [1, 0.3]], }) extra_str = json.dumps( {"request_id": "abcd1234"} ) -file_url = "/Users/root/demo/xiaoxingxing.aces" -files = [('file', open(file_url, 'rb'))] +# Submit several pieces at once by repeating ('file', ...) +files = [ + ('file', open("/path/to/piece_1.aces", 'rb')), + ('file', open("/path/to/piece_2.aces", 'rb')), +] data_dict = { "ace_token": "XXXXXXXXXXXXXXXX", "cooperator": "XXXXXXXXXXXX", - "speaker_id": "3", + # Single singer via speaker_id (see the singer list); mix_info takes precedence if both are given + "speaker_id": "82", "mix_info": mix_str, "extra": extra_str, } @@ -79,10 +83,27 @@ resp = requests.request("POST", url=url, files=files, data=data_dict) Data format explanation: -| Parameter Name | Type | Description | -|----------------|--------|---------------------------------------------------------------------| -| audio | string | Audio URL returned | -| pst | number | Start time of the audio (calculated based on the notes in the file) | +| Parameter Name | Type | Description | +|----------------------|--------|--------------------------------------------------------------------------------------------------| +| audio | string | Pre-signed download URL for the audio; GET it directly, no auth header needed | +| pst | number | Absolute time (seconds) of **sample 0** of this audio on the original project timeline | +| output_format_suffix | string | Audio file suffix, `ogg` by default | +| sequence_index | number | Index of the corresponding uploaded file, starting at 0, matching the order of the `file` fields | + +**Audio format**: Ogg/Opus container by default, **48kHz**, VBR targeting 64kbps +(measured around 70kbps on long pieces). + +> The engine synthesizes internally at 44.1kHz, but Opus only supports 48kHz natively, so the +> encoder resamples to 48kHz — the file you download has a sample rate of 48000. Resample on +> your side if you need 44.1kHz material. + +**The download URL is valid for 48 hours** (172800 seconds); fetch and store the audio within that +window. The service does **not** cache synthesis results — resubmitting the same request +synthesises again (and bills again), and the audio is not guaranteed to be byte-identical. + +**Submitting several pieces**: repeat the multipart `file` field; the returned `data` array maps to +the submission order via `sequence_index`. If **any single piece fails the whole request fails** +(not billed, and successfully rendered pieces are not returned) — retry the request as a whole. ```json { @@ -107,7 +128,9 @@ Data format explanation: ### 2. Quota statistics -- Request:`https://gateway-us.svsbusiness.com/bill/quota` +- Request URL (not on the same host as the synthesis endpoint — use the one for your node): + - China: `https://gateway.svsbusiness.com/bill/quota` + - Overseas: `https://gateway-us.svsbusiness.com/bill/quota` - Request Method:`GET` #### Request Parameters @@ -152,6 +175,29 @@ Data format explanation: | used_amount | number | The amount already used by this token | | qps | number | The limit on the number of tokens synthesized per second for this token | +**Billing granularity** + +- One **successful** synthesis request consumes exactly **1 credit**, regardless of the number of + pieces submitted, the duration of each piece, or how many singers are blended. +- Requests returning a non-200 code are **not billed** (400 / 429 / 453 / 503 consume no credit). +- `charging_strategy = 1` (quantity-based): each successful request increments `used_amount` by 1; + once it reaches `billing_balance` the synthesis endpoint returns 400. +- `charging_strategy = 2` (time-package): credits are not decremented per call; only + `charging_expire_time` is checked, after which the endpoint returns 400. + +> **Credits are deducted asynchronously.** After a request returns `200`, `used_amount` takes a +> few seconds to a few tens of seconds to update. Querying the quota immediately after a request +> shows the old value; that is normal and does not mean the request was not billed. + +There are therefore two ways to reduce credit consumption, in order of importance: + +1. **First, fit the content into as few files as possible.** The per-file limit is 90 seconds + (see "4. Synthesis Constraints"). The engine infers on a fixed-length canvas, so synthesizing + 5 seconds and 90 seconds cost about the same; slicing content up only makes the same audio + go through inference repeatedly. +2. **Then batch several files into one request** (up to 3). The trade-off is that a single + failing piece requires retrying the whole request. + ```json { "data": [ @@ -174,20 +220,29 @@ Data format explanation: ``` ### 3. Response Status Codes -| Status Code | Description | -|-------------|--------------------------------------------------------------------| -| 200 | Request successful | -| 503 | Number of concurrent requests exceeds limit | -| 400 | Request parameters do not conform to the documentation | -| 429 | Invalid token | -| 402 | Synthesis engine exception, mostly due to extreme data in the file | -| 453 | Internal server error | +| Status Code | Description | +|-------------|--------------------------------------------------------------------------------------| +| 200 | Request successful | +| 400 | Request parameters or file content do not conform to the documentation; insufficient quota; qps limit exceeded | +| 429 | Invalid token (cannot be decrypted) | +| 453 | Data validation failed (the file contains extreme data) or synthesis engine exception | +| 503 | Number of concurrent requests exceeds limit | + +The HTTP status code always matches the `code` field in the response body. On failure `data` +is `null` and the reason is in `error`, which usually contains the timestamp of the offending +note and the field name so you can locate the problem. + +> **Exception**: `503` is returned by the gateway before the request reaches the service, so its +> body is **not** JSON (it may be HTML). Check the HTTP status code before parsing the body — +> do not assume `resp.json()` works for 503. + +> return `453`. ### 4. Synthesis Constraints | Constraint | Value | Description | |-----------------------------------|--------------------------------|---------------------------------------------------------------| -| Limit on the number of pieces | 4 | The number of pieces in each request cannot exceed this limit | -| Limit on the length of each piece | 18s | The length of each piece cannot exceed this time limit | -| Concurrent request limit | 20 | Exceeding this limit will result in a 503 error | -| query per second for single token | default 3,contact us to adjust | Exceeding this limit will not be allowed | +| Limit on the number of pieces | 3 | The number of pieces in each request cannot exceed this limit | +| Limit on the length of each piece | 90s | Measured as the notes time span; a 1200-phoneme cap also applies, see [ACES file specification](/docs/aces_file_en.md) 5.8 | +| Concurrent request limit | 20 | **Node-level** cap shared by all customers; the gateway returns 503 beyond it — back off and retry | +| Queries per second per token | 3 by default, contact us to adjust | Enforced as an average over a **60-second rolling window** (rejected when requests in the window exceed qps x 60); returns 400 | diff --git a/api/docs/singer_demos/0.ogg b/api/docs/singer_demos/0.ogg new file mode 100644 index 0000000..55774e2 Binary files /dev/null and b/api/docs/singer_demos/0.ogg differ diff --git a/api/docs/singer_demos/1.ogg b/api/docs/singer_demos/1.ogg new file mode 100644 index 0000000..a7b8350 Binary files /dev/null and b/api/docs/singer_demos/1.ogg differ diff --git a/api/docs/singer_demos/100.ogg b/api/docs/singer_demos/100.ogg new file mode 100644 index 0000000..fb2f370 Binary files /dev/null and b/api/docs/singer_demos/100.ogg differ diff --git a/api/docs/singer_demos/101.ogg b/api/docs/singer_demos/101.ogg new file mode 100644 index 0000000..041a3a1 Binary files /dev/null and b/api/docs/singer_demos/101.ogg differ diff --git a/api/docs/singer_demos/102.ogg b/api/docs/singer_demos/102.ogg new file mode 100644 index 0000000..944ce1a Binary files /dev/null and b/api/docs/singer_demos/102.ogg differ diff --git a/api/docs/singer_demos/103.ogg b/api/docs/singer_demos/103.ogg new file mode 100644 index 0000000..c1b8fc1 Binary files /dev/null and b/api/docs/singer_demos/103.ogg differ diff --git a/api/docs/singer_demos/104.ogg b/api/docs/singer_demos/104.ogg new file mode 100644 index 0000000..7cfa416 Binary files /dev/null and b/api/docs/singer_demos/104.ogg differ diff --git a/api/docs/singer_demos/105.ogg b/api/docs/singer_demos/105.ogg new file mode 100644 index 0000000..e639322 Binary files /dev/null and b/api/docs/singer_demos/105.ogg differ diff --git a/api/docs/singer_demos/106.ogg b/api/docs/singer_demos/106.ogg new file mode 100644 index 0000000..7018957 Binary files /dev/null and b/api/docs/singer_demos/106.ogg differ diff --git a/api/docs/singer_demos/107.ogg b/api/docs/singer_demos/107.ogg new file mode 100644 index 0000000..98a5efb Binary files /dev/null and b/api/docs/singer_demos/107.ogg differ diff --git a/api/docs/singer_demos/108.ogg b/api/docs/singer_demos/108.ogg new file mode 100644 index 0000000..2ca60f6 Binary files /dev/null and b/api/docs/singer_demos/108.ogg differ diff --git a/api/docs/singer_demos/109.ogg b/api/docs/singer_demos/109.ogg new file mode 100644 index 0000000..371d8cf Binary files /dev/null and b/api/docs/singer_demos/109.ogg differ diff --git a/api/docs/singer_demos/110.ogg b/api/docs/singer_demos/110.ogg new file mode 100644 index 0000000..7ad8f8f Binary files /dev/null and b/api/docs/singer_demos/110.ogg differ diff --git a/api/docs/singer_demos/130.ogg b/api/docs/singer_demos/130.ogg new file mode 100644 index 0000000..268b4f5 Binary files /dev/null and b/api/docs/singer_demos/130.ogg differ diff --git a/api/docs/singer_demos/131.ogg b/api/docs/singer_demos/131.ogg new file mode 100644 index 0000000..a5329c8 Binary files /dev/null and b/api/docs/singer_demos/131.ogg differ diff --git a/api/docs/singer_demos/132.ogg b/api/docs/singer_demos/132.ogg new file mode 100644 index 0000000..51a3e9a Binary files /dev/null and b/api/docs/singer_demos/132.ogg differ diff --git a/api/docs/singer_demos/133.ogg b/api/docs/singer_demos/133.ogg new file mode 100644 index 0000000..0c65e94 Binary files /dev/null and b/api/docs/singer_demos/133.ogg differ diff --git a/api/docs/singer_demos/134.ogg b/api/docs/singer_demos/134.ogg new file mode 100644 index 0000000..1ae9748 Binary files /dev/null and b/api/docs/singer_demos/134.ogg differ diff --git a/api/docs/singer_demos/18.ogg b/api/docs/singer_demos/18.ogg new file mode 100644 index 0000000..bf2c1a2 Binary files /dev/null and b/api/docs/singer_demos/18.ogg differ diff --git a/api/docs/singer_demos/2.ogg b/api/docs/singer_demos/2.ogg new file mode 100644 index 0000000..eb736fd Binary files /dev/null and b/api/docs/singer_demos/2.ogg differ diff --git a/api/docs/singer_demos/24.ogg b/api/docs/singer_demos/24.ogg new file mode 100644 index 0000000..9502e82 Binary files /dev/null and b/api/docs/singer_demos/24.ogg differ diff --git a/api/docs/singer_demos/26.ogg b/api/docs/singer_demos/26.ogg new file mode 100644 index 0000000..382ba29 Binary files /dev/null and b/api/docs/singer_demos/26.ogg differ diff --git a/api/docs/singer_demos/29.ogg b/api/docs/singer_demos/29.ogg new file mode 100644 index 0000000..19056f1 Binary files /dev/null and b/api/docs/singer_demos/29.ogg differ diff --git a/api/docs/singer_demos/4.ogg b/api/docs/singer_demos/4.ogg new file mode 100644 index 0000000..6509aae Binary files /dev/null and b/api/docs/singer_demos/4.ogg differ diff --git a/api/docs/singer_demos/46.ogg b/api/docs/singer_demos/46.ogg new file mode 100644 index 0000000..0635c71 Binary files /dev/null and b/api/docs/singer_demos/46.ogg differ diff --git a/api/docs/singer_demos/49.ogg b/api/docs/singer_demos/49.ogg new file mode 100644 index 0000000..4e2f2d7 Binary files /dev/null and b/api/docs/singer_demos/49.ogg differ diff --git a/api/docs/singer_demos/5.ogg b/api/docs/singer_demos/5.ogg new file mode 100644 index 0000000..b246d8f Binary files /dev/null and b/api/docs/singer_demos/5.ogg differ diff --git a/api/docs/singer_demos/7.ogg b/api/docs/singer_demos/7.ogg new file mode 100644 index 0000000..7e82715 Binary files /dev/null and b/api/docs/singer_demos/7.ogg differ diff --git a/api/docs/singer_demos/76.ogg b/api/docs/singer_demos/76.ogg new file mode 100644 index 0000000..5bd00fa Binary files /dev/null and b/api/docs/singer_demos/76.ogg differ diff --git a/api/docs/singer_demos/80.ogg b/api/docs/singer_demos/80.ogg new file mode 100644 index 0000000..f1146e2 Binary files /dev/null and b/api/docs/singer_demos/80.ogg differ diff --git a/api/docs/singer_demos/81.ogg b/api/docs/singer_demos/81.ogg new file mode 100644 index 0000000..af943fa Binary files /dev/null and b/api/docs/singer_demos/81.ogg differ diff --git a/api/docs/singer_demos/82.ogg b/api/docs/singer_demos/82.ogg new file mode 100644 index 0000000..81c85c9 Binary files /dev/null and b/api/docs/singer_demos/82.ogg differ diff --git a/api/docs/singer_demos/83.ogg b/api/docs/singer_demos/83.ogg new file mode 100644 index 0000000..a2937fc Binary files /dev/null and b/api/docs/singer_demos/83.ogg differ diff --git a/api/docs/singer_demos/84.ogg b/api/docs/singer_demos/84.ogg new file mode 100644 index 0000000..c001e81 Binary files /dev/null and b/api/docs/singer_demos/84.ogg differ diff --git a/api/docs/singer_demos/85.ogg b/api/docs/singer_demos/85.ogg new file mode 100644 index 0000000..078ac58 Binary files /dev/null and b/api/docs/singer_demos/85.ogg differ diff --git a/api/docs/singer_demos/89.ogg b/api/docs/singer_demos/89.ogg new file mode 100644 index 0000000..85669ff Binary files /dev/null and b/api/docs/singer_demos/89.ogg differ diff --git a/api/docs/singer_info.md b/api/docs/singer_info.md index 8241929..07fa952 100644 --- a/api/docs/singer_info.md +++ b/api/docs/singer_info.md @@ -1,78 +1,90 @@ -| 歌手名称 | 性别 | 歌手id | 音色特点 | 原生语言 | -|------------|----|------|----------|------| -| 小夜 | 女 | 0 | 流行、温暖 | 中文 | -| 火涟 | 女 | 1 | 日系、高能 | 中文 | -| 云灏 | 男 | 2 | 流行、阳光 | 中文 | -| 鲤沅 | 女 | 4 | 民歌、华丽 | 中文 | -| 小莫 | 女 | 5 | 流行、甜美 | 中文 | -| 绮萱 | 女 | 7 | 童声、可爱 | 中文 | -| 文栗 | 男 | 18 | R&B、慵懒 | 中文 | -| Steel | 男 | 24 | 摇滚、韩系 | 中文 | -| Ghost | 男 | 26 | 摇滚、暗黑 | 中文 | -| 绯 | 男 | 29 | 流行、松弛 | 中文 | -| 缇 | 男 | 46 | 流行、国风 | 中文 | -| Barber | 男 | 76 | 美声、浑厚 | 中文 | -| David | 男 | 80 | 流行、抒情 | 英文 | -| Lien | 女 | 81 | 流行、抒情 | 英文 | -| Bianca | 女 | 82 | 流行、热烈 | 英文 | -| Naples | 男 | 83 | 摇滚、高能 | 英文 | -| Sidney | 男 | 84 | Disco、明亮 | 英文 | -| Drex L | 男 | 85 | 流行、R&B | 英文 | -| Ember Rose | 女 | 89 | 流行、情歌 | 英文 | -| Felix | 男 | 100 | 民歌、拉丁 | 西语 | -| Maximus | 男 | 101 | 流行、情歌 | 西语 | -| Livia | 女 | 102 | 拉丁、情歌 | 西语 | -| Celestia | 女 | 103 | 民歌、情歌 | 西语 | -| Valentina | 女 | 104 | 民歌、情歌 | 西语 | -| Dominic | 男 | 105 | 摇滚、流行 | 西语 | -| Kiza | 男 | 106 | 拉丁、雷鬼 | 西语 | -| Marco | 男 | 107 | 流行、情歌 | 西语 | -| Micky | 女 | 108 | 爵士、民歌 | 西语 | -| Julian | 男 | 109 | 拉丁、流行 | 西语 | -| Draco | 男 | 110 | Rock、流行 | 西语 | -| Franklin | 男 | 130 | 流行、情歌 | 英文 | -| Zalo | 女 | 131 | R&B、灵魂 | 英文 | -| Elirah | 女 | 132 | R&B、流行 | 英文 | -| Jessica | 女 | 133 | R&B、流行 | 英文 | -| Emma | 女 | 134 | 流行、情歌 | 英文 | +> **试听样音**:每位歌手都有一段 6 秒的样音(`api/docs/singer_demos/<歌手id>.ogg`)。 +> 同一原生语言的歌手唱同一句、同一旋律,方便直接对比音色:中文唱"春风吹过山河", +> 英文唱 "let the music carry",西语唱 "camino de la luz"。 +> 女声在 G4 附近、男声在 A3 附近起唱,末音延长 2 秒便于听持续音的质感。 + +| 歌手名称 | 性别 | 歌手id | 音色特点 | 原生语言 | 试听 | +|------------|----|------|----------|------ |------| +| 小夜 | 女 | 0 | 流行、温暖 | 中文 | [▶](/api/docs/singer_demos/0.ogg) | +| 火涟 | 女 | 1 | 日系、高能 | 中文 | [▶](/api/docs/singer_demos/1.ogg) | +| 云灏 | 男 | 2 | 流行、阳光 | 中文 | [▶](/api/docs/singer_demos/2.ogg) | +| 鲤沅 | 女 | 4 | 民歌、华丽 | 中文 | [▶](/api/docs/singer_demos/4.ogg) | +| 小莫 | 女 | 5 | 流行、甜美 | 中文 | [▶](/api/docs/singer_demos/5.ogg) | +| 绮萱 | 女 | 7 | 童声、可爱 | 中文 | [▶](/api/docs/singer_demos/7.ogg) | +| 文栗 | 男 | 18 | R&B、慵懒 | 中文 | [▶](/api/docs/singer_demos/18.ogg) | +| Steel | 男 | 24 | 摇滚、韩系 | 中文 | [▶](/api/docs/singer_demos/24.ogg) | +| Ghost | 男 | 26 | 摇滚、暗黑 | 中文 | [▶](/api/docs/singer_demos/26.ogg) | +| 绯 | 男 | 29 | 流行、松弛 | 中文 | [▶](/api/docs/singer_demos/29.ogg) | +| 缇 | 男 | 46 | 流行、国风 | 中文 | [▶](/api/docs/singer_demos/46.ogg) | +| Growl | 男 | 49 | 流行、厚重 | 中文 | [▶](/api/docs/singer_demos/49.ogg) | +| Barber | 男 | 76 | 美声、浑厚 | 中文 | [▶](/api/docs/singer_demos/76.ogg) | +| David | 男 | 80 | 流行、抒情 | 英文 | [▶](/api/docs/singer_demos/80.ogg) | +| Lien | 女 | 81 | 流行、抒情 | 英文 | [▶](/api/docs/singer_demos/81.ogg) | +| Bianca | 女 | 82 | 流行、热烈 | 英文 | [▶](/api/docs/singer_demos/82.ogg) | +| Naples | 男 | 83 | 摇滚、高能 | 英文 | [▶](/api/docs/singer_demos/83.ogg) | +| Sidney | 男 | 84 | Disco、明亮 | 英文 | [▶](/api/docs/singer_demos/84.ogg) | +| Drex L | 男 | 85 | 流行、R&B | 英文 | [▶](/api/docs/singer_demos/85.ogg) | +| Ember Rose | 女 | 89 | 流行、情歌 | 英文 | [▶](/api/docs/singer_demos/89.ogg) | +| Felix | 男 | 100 | 民歌、拉丁 | 西语 | [▶](/api/docs/singer_demos/100.ogg) | +| Maximus | 男 | 101 | 流行、情歌 | 西语 | [▶](/api/docs/singer_demos/101.ogg) | +| Livia | 女 | 102 | 拉丁、情歌 | 西语 | [▶](/api/docs/singer_demos/102.ogg) | +| Celestia | 女 | 103 | 民歌、情歌 | 西语 | [▶](/api/docs/singer_demos/103.ogg) | +| Valentina | 女 | 104 | 民歌、情歌 | 西语 | [▶](/api/docs/singer_demos/104.ogg) | +| Dominic | 男 | 105 | 摇滚、流行 | 西语 | [▶](/api/docs/singer_demos/105.ogg) | +| Kiza | 男 | 106 | 拉丁、雷鬼 | 西语 | [▶](/api/docs/singer_demos/106.ogg) | +| Marco | 男 | 107 | 流行、情歌 | 西语 | [▶](/api/docs/singer_demos/107.ogg) | +| Micky | 女 | 108 | 爵士、民歌 | 西语 | [▶](/api/docs/singer_demos/108.ogg) | +| Julian | 男 | 109 | 拉丁、流行 | 西语 | [▶](/api/docs/singer_demos/109.ogg) | +| Draco | 男 | 110 | Rock、流行 | 西语 | [▶](/api/docs/singer_demos/110.ogg) | +| Franklin | 男 | 130 | 流行、情歌 | 英文 | [▶](/api/docs/singer_demos/130.ogg) | +| Zalo | 女 | 131 | R&B、灵魂 | 英文 | [▶](/api/docs/singer_demos/131.ogg) | +| Elirah | 女 | 132 | R&B、流行 | 英文 | [▶](/api/docs/singer_demos/132.ogg) | +| Jessica | 女 | 133 | R&B、流行 | 英文 | [▶](/api/docs/singer_demos/133.ogg) | +| Emma | 女 | 134 | 流行、情歌 | 英文 | [▶](/api/docs/singer_demos/134.ogg) | --------- -| Singer Name | Gender | Singer ID | Vocal Characteristics | Original Language | -|-------------|--------|-----------|-----------------------|-------------------| -| Xiao Ye | Female | 0 | Pop、Warm | Chinese | -| Huo Lian | Female | 1 | JPop、Powerful | Chinese | -| Yun Hao | Male | 2 | Pop、Gentle | Chinese | -| Li Yuan | Female | 4 | C-Fork、Gorgeous | Chinese | -| Xiao Mo | Female | 5 | Pop、Sweet | Chinese | -| Qi Xuan | Female | 7 | Childish、Cute | Chinese | -| Wen Li | Male | 18 | R&B、Lazy | Chinese | -| Steel | Male | 24 | Pop、Mantle | Chinese | -| Ghost | Male | 26 | Pop、Dark | Chinese | -| Crimson | Male | 29 | Pop、Soft | Chinese | -| Tangrine | Male | 46 | Pop、Lyrical | Chinese | -| Growl | Male | 49 | Pop、Heavy | Chinese | -| Barber | Male | 76 | Bel Canto、Gentle | Chinese | -| David | Male | 80 | Pop、Gentle | English | -| Lien | Female | 81 | Pop、Lyrical | English | -| Bianca | Female | 82 | Pop、Hot | English | -| Naples | Male | 83 | Rock、Powerful | English | -| Sidney | Male | 84 | Disco、Bright | English | -| Drex L | Male | 85 | Pop、R&B | English | -| Ember Rose | Female | 89 | Pop、Clear | English | -| Felix | Male | 100 | Folk、Latin | Spanish | -| Maximus | Male | 101 | Pop、Ballad | Spanish | -| Livia | Female | 102 | Latin、Ballad | Spanish | -| Celestia | Female | 103 | Folk、Ballad | Spanish | -| Valentina | Female | 104 | Folk、Ballad | Spanish | -| Dominic | Male | 105 | Rock、Pop | Spanish | -| Kiza | Male | 106 | Latin、Reggaeton | Spanish | -| Marco | Male | 107 | Pop、Ballad | Spanish | -| Micky | Female | 108 | Jazz、Folk | Spanish | -| Julian | Male | 109 | Latin、Pop | Spanish | -| Draco | Male | 110 | Rock、Pop | Spanish | -| Franklin | Male | 130 | Pop、Ballad | English | -| Zalo | Female | 131 | R&B、Soul | English | -| Elirah | Female | 132 | R&B、Pop | English | -| Jessica | Female | 133 | R&B、Pop | English | -| Emma | Female | 134 | Pop、Ballad | English | +> **Demos**: every singer has a 6-second sample at `api/docs/singer_demos/.ogg`. +> Singers sharing a native language sing the same line on the same melody so you can compare +> timbres directly: Chinese sings "春风吹过山河", English "let the music carry", Spanish +> "camino de la luz". Female voices start around G4 and male voices around A3, with the last +> note held for 2 seconds so you can hear the sustained tone. + +| Singer Name | Gender | Singer ID | Vocal Characteristics | Original Language | Demo | +|-------------|--------|-----------|-----------------------|------------------- |------| +| Xiao Ye | Female | 0 | Pop、Warm | Chinese | [▶](/api/docs/singer_demos/0.ogg) | +| Huo Lian | Female | 1 | JPop、Powerful | Chinese | [▶](/api/docs/singer_demos/1.ogg) | +| Yun Hao | Male | 2 | Pop、Gentle | Chinese | [▶](/api/docs/singer_demos/2.ogg) | +| Li Yuan | Female | 4 | C-Fork、Gorgeous | Chinese | [▶](/api/docs/singer_demos/4.ogg) | +| Xiao Mo | Female | 5 | Pop、Sweet | Chinese | [▶](/api/docs/singer_demos/5.ogg) | +| Qi Xuan | Female | 7 | Childish、Cute | Chinese | [▶](/api/docs/singer_demos/7.ogg) | +| Wen Li | Male | 18 | R&B、Lazy | Chinese | [▶](/api/docs/singer_demos/18.ogg) | +| Steel | Male | 24 | Pop、Mantle | Chinese | [▶](/api/docs/singer_demos/24.ogg) | +| Ghost | Male | 26 | Pop、Dark | Chinese | [▶](/api/docs/singer_demos/26.ogg) | +| Crimson | Male | 29 | Pop、Soft | Chinese | [▶](/api/docs/singer_demos/29.ogg) | +| Tangrine | Male | 46 | Pop、Lyrical | Chinese | [▶](/api/docs/singer_demos/46.ogg) | +| Growl | Male | 49 | Pop、Heavy | Chinese | [▶](/api/docs/singer_demos/49.ogg) | +| Barber | Male | 76 | Bel Canto、Gentle | Chinese | [▶](/api/docs/singer_demos/76.ogg) | +| David | Male | 80 | Pop、Gentle | English | [▶](/api/docs/singer_demos/80.ogg) | +| Lien | Female | 81 | Pop、Lyrical | English | [▶](/api/docs/singer_demos/81.ogg) | +| Bianca | Female | 82 | Pop、Hot | English | [▶](/api/docs/singer_demos/82.ogg) | +| Naples | Male | 83 | Rock、Powerful | English | [▶](/api/docs/singer_demos/83.ogg) | +| Sidney | Male | 84 | Disco、Bright | English | [▶](/api/docs/singer_demos/84.ogg) | +| Drex L | Male | 85 | Pop、R&B | English | [▶](/api/docs/singer_demos/85.ogg) | +| Ember Rose | Female | 89 | Pop、Clear | English | [▶](/api/docs/singer_demos/89.ogg) | +| Felix | Male | 100 | Folk、Latin | Spanish | [▶](/api/docs/singer_demos/100.ogg) | +| Maximus | Male | 101 | Pop、Ballad | Spanish | [▶](/api/docs/singer_demos/101.ogg) | +| Livia | Female | 102 | Latin、Ballad | Spanish | [▶](/api/docs/singer_demos/102.ogg) | +| Celestia | Female | 103 | Folk、Ballad | Spanish | [▶](/api/docs/singer_demos/103.ogg) | +| Valentina | Female | 104 | Folk、Ballad | Spanish | [▶](/api/docs/singer_demos/104.ogg) | +| Dominic | Male | 105 | Rock、Pop | Spanish | [▶](/api/docs/singer_demos/105.ogg) | +| Kiza | Male | 106 | Latin、Reggaeton | Spanish | [▶](/api/docs/singer_demos/106.ogg) | +| Marco | Male | 107 | Pop、Ballad | Spanish | [▶](/api/docs/singer_demos/107.ogg) | +| Micky | Female | 108 | Jazz、Folk | Spanish | [▶](/api/docs/singer_demos/108.ogg) | +| Julian | Male | 109 | Latin、Pop | Spanish | [▶](/api/docs/singer_demos/109.ogg) | +| Draco | Male | 110 | Rock、Pop | Spanish | [▶](/api/docs/singer_demos/110.ogg) | +| Franklin | Male | 130 | Pop、Ballad | English | [▶](/api/docs/singer_demos/130.ogg) | +| Zalo | Female | 131 | R&B、Soul | English | [▶](/api/docs/singer_demos/131.ogg) | +| Elirah | Female | 132 | R&B、Pop | English | [▶](/api/docs/singer_demos/132.ogg) | +| Jessica | Female | 133 | R&B、Pop | English | [▶](/api/docs/singer_demos/133.ogg) | +| Emma | Female | 134 | Pop、Ballad | English | [▶](/api/docs/singer_demos/134.ogg) | diff --git a/docs/aces_file.md b/docs/aces_file.md index 4b9a2b4..c6d04ea 100644 --- a/docs/aces_file.md +++ b/docs/aces_file.md @@ -28,8 +28,8 @@ | start_time | number | 是 | 音符开始时间,以秒为单位 | | end_time | number | 是 | 音符结束时间,以秒为单位 | | type | string | 否,默认为"general" | 音符类型,详见音符类型说明 | -| pitch | number | 否 | 音高值,详见音高值说明 | -| language | string | 否,默认为ch | 音符语言:中文"ch",英语"en",日语"jp" | +| pitch | number | type 为 general 时必传 | 音高值,详见音高值说明。`slur` 可省略(继承前一个发音音符的音高);`br`/`sp` 忽略该字段 | +| language | string | 否,默认为ch | 音符语言,取值见 5.3(`ch` 中文 / `jp` 日语 / `en` 英语 / `spa` 西班牙语) | | phone | Array | 否 | 当前note音素列表,详见音素说明 | | syllable | string | 否 | 当前note音节,详见音节说明 | @@ -52,7 +52,12 @@ ### 2.4 音节说明 -语言为中文或者日语时才可以使用,可以直接使用音节而无需传音素列表 +**仅中文(`language` 为 `ch`)支持音节输入**,可以直接给拼音(如 `"la"`、`"shan"`) +或单个汉字(如 `"星"`),服务会自动转换成音素,无需传 `phone`。 + +- 汉字为多音字、或拼音不合法时会返回 `400`,请改用拼音或直接给 `phone`。 +- **其它语言(英语/日语/西班牙语等)请直接给 `phone` 列表**。这些语言不做音节转换, + 只传 `syllable` 会得到一个默认发音(不是您期望的歌词)。 *示例:* @@ -84,10 +89,22 @@ ## 3. PIECE_PARAMS: 片段参数(实验性功能) -| 字段名 | 字段类型 | 是否必传 | 说明 | -|--------|--------|------|---------------| -| pitch | Object | 否 | 详见 `pitch` 对象 | -| energy | Object | 否 | 详见 `param` 对象 | +| 字段名 | 字段类型 | 是否必传 | 说明 | +|----------|--------|------|---------------| +| pitch | Object | 否 | 详见 `pitch` 对象 | +| energy | Object | 否 | 详见 `param` 对象 | +| air | Object | 否 | 详见 `param` 对象,气息含量 | +| falsetto | Object | 否 | 详见 `param` 对象,假声含量 | +| tension | Object | 否 | 详见 `param` 对象,声带紧张度 | + +> 生效范围:`pitch` 的 `user` / `delta` 层,以及 +> `energy` / `air` / `falsetto` / `tension` 的 `user` 层。 +> 这五个参数、这两种层就是全部;其余字段一律返回 `400` 并说明原因。 + +可直接提交的样例: + +- `examples/vibrato_example.aces` —— `pitch` 的 `user` + `delta`(颤音) +- `examples/param_example.aces` —— `energy` / `air` / `falsetto` / `tension` 的 `user` 曲线 ### 3.1 PITCH: 音高表示 @@ -96,6 +113,24 @@ | user | Array(PIECE_VALUE) | 否 | 用户自定义音高线, 见 `piece_value` 对象,取值范围30-90 | | delta | Array(PIECE_VALUE) | 否 | 用户自定义音高线偏移, 见 `piece_value` 对象,取值范围[-4,4] | +> **`delta` 必须与 `user` 同时给出。** `delta` 是叠加在 `user` 音高线之上的修正量, +> 只给 `delta` 而不给 `user` 时该层会被忽略(不会报错,但颤音等效果不会生效)。 +> 未被 `user` 覆盖的时间段由模型自行预测音高。 + +> **`user` 覆盖到的时间段,音高完全由您接管**,未覆盖处才交给模型预测。因此建议让 +> `user` 覆盖整个音符:如果只覆盖颤音所在的后半段,模型预测段与 `user` 段的交界处 +> 会出现接近一个半音的音高台阶(实测约 100 cent),听上去是一声突跳。 + +> **长音默认就带模型生成的自然颤音**(实测约 5~6Hz、深度约 ±0.5 半音)。只有需要精确 +> 控制颤音的起振时刻、频率和深度时才需要传 `pitch`;只是想"有颤音"的话不传即可。 +> 这也意味着:只给 `delta` 而漏了 `user` 时,您听到的颤音是模型的默认行为, +> 并不是您写入的那条曲线。 + +`examples/vibrato_example.aces` 是一个完整例子:一个 4.75~6.00s 的音符,`user` 用 +25 个点铺一条恒为 63 的水平音高线覆盖整个音符,`delta` 从 5.29s 起叠加一条 6Hz 的 +正弦,于是听感上是"先直音、后颤音"。为了让效果足够明显,该样例的颤音深度取到了 +±1.8 半音,实际歌唱通常在 ±0.3~0.7 半音。 + 示例: ``` @@ -107,17 +142,24 @@ ### 3.2 PARAM: 参数表示 -| 字段名 | 字段类型 | 是否必传 | 说明 | -|----------|---------------------|------|------------------------------------------| -| user | Array(PIECE_VALUE) | 否 | 自定义参数线, 见 `piece_value` 对象, 取值范围根据参数类型决定 | -| envelope | Array(PIECE_VALUE) | 否 | 参数包络线, 见 `piece_value` 对象,取值范围0-2 | +| 字段名 | 字段类型 | 是否必传 | 说明 | +|------|--------------------|------|-------------------------------------| +| user | Array(PIECE_VALUE) | 否 | 自定义参数线, 见 `piece_value` 对象,取值范围 0~1 | + +**四个参数的 `user` 取值范围统一为 0~1**,数组中的负值表示"该处不指定,交给模型预测"。 +超出 0~1 的值会被截断,不报错。 + +四个参数在听感上的作用:`energy` 音量与力度;`air` 气声成分;`falsetto` 假声含量 +(会削弱高次谐波,声音变柔);`tension` 声带紧张度(拉高亮度,四者中效果最明显)。 + +`examples/param_example.aces` 演示了这四条曲线的写法:4 个同音高的长音,每个音符上 +只给一个参数做"低→高"的渐变,方便逐个试听单一参数的作用。 示例: ``` { - "user": [PIECE_VALUE], - "envelope": [PIECE_VALUE] + "user": [PIECE_VALUE] } ``` @@ -129,6 +171,13 @@ | hop_time | number | 是 | values数组中每两个数据帧之间的间隔 | | values | Array | 是 | 取值数组,根据不同的值类型具有不同的取值范围 | +该段覆盖的时间范围是 `start_time` 到 `start_time + len(values) * hop_time`。 + +> `values` 会被重采样到引擎内部帧率(约 5.8ms 一帧),因此 `hop_time` 由您自行决定: +> 变化平缓的曲线用粗栅格(如 0.05s)就够了,不必逐帧给点。同一个 `piece_params` +> 下的不同层(如 `pitch` 的 `user` 与 `delta`)也不要求使用相同的 `hop_time`, +> 各层按各自的 `start_time` / `hop_time` 独立对齐到时间轴后再叠加。 + 示例: ``` @@ -168,6 +217,11 @@ 说明: pad属于额外信息,一般情况下可不填。当ACES文件用于深度学习模型合成歌声时,可以加入此片段外前后的音符信息,用来获得更佳的合成效果 +> `pad` 完全可以不传 —— 服务会根据 notes 的首尾时间自动补齐, +> 并按内部帧栅格对齐。若传了 `pad`,其中的 `type`(`sp` / `br` / `sil` 等)会被采用, +> 但 `start_time` / `end_time` 会被重新计算。 +> 字段名写成 `pad_notes` 也同样被接受。 + 示例: ``` @@ -181,13 +235,61 @@ pad属于额外信息,一般情况下可不填。当ACES文件用于深度学 *定义:* 每个note只有唯一的元音,本note内该元音之前的辅音都称为pre_consonant,本note内该元音之后的辅音都称为post_consonant -### 5.1 每个note必须有足够的长度,小于0.02s的note会有很大概率发生合成异常 +### 5.1 每个note必须有足够的长度容纳它的音素 +音素时长的最小单位是一帧(约 5.8ms)。**一个 note 的音素个数不能超过它的时长所对应的帧数**, +例如 16ms 的 note(约 2 帧)最多只能有 2 个音素,塞 3 个会返回 `453` 并指出该音符的时间。 +一般建议每个 note 不短于 0.05s。 + ### 5.2 pitch必须在30到90的区间 -### 5.3 language字段只支持ch\en\jp\spa +440Hz 标准音 = 69。**这是硬校验区间**,超出返回 `453` 并给出具体音符时间。 +区间之外的音高在引擎里会被当作无效基频处理(听感是不发声或怪音),因此在校验层 +直接拒绝,而不是放行后让您拿到一段不可用的音频。 +### 5.3 language 字段支持 ch / jp / en / spa +分别是中文、日语、英语、西班牙语。不在列表内的取值会返回 `400`。 +每种语言各有一份合法音素表,见 5.4。 ### 5.4 每个note的每个phone是必须是合法的,不同语言的合法phone列表是不一样的 +音素表见 https://github.com/timedomain-tech/ACE_phonemes 。 +英语 / 日语 / 西班牙语可以用 `api/demo/lyrics2phone.py` 从歌词自动生成音素,见 README。 +表外的音素会返回 `453` 并列出无法识别的音素名。 ### 5.5 每个note必须正好有一个元音 -### 5.6 如果要调节consonant_time_head或者consonant_time_tail需要认真核对:本note中的pre_consonant数量与consonant_time_head中元素数量相等;本note中的post_consonant数量与consonant_time_tail中元素数量相等 -### 5.7 由于内部逻辑,我们会将每个note的post_consonant与下一个note的pre_consonant合并计算,因此需要保证:本note的长度>本note的consonant_time_tail长度+下一个note的consonant_time_head长度 -### 5.8 每个aces文件只能合成不大于18s的notes列表 -### 5.9 必须提供speaker信息 -### 5.10 如果要使用piece_params,需要认真核对piece_params的时间范围不能超过note列表的时间范围 \ No newline at end of file +没有元音的 note 会返回 `453`(否则该字会不发音)。 +### 5.6 `slur`(延音)必须与它所延长的音符严格首尾相接 +`slur.start_time` 必须等于前一个发音音符(`general` 或 `slur`)的 `end_time`(容差 1ms), +两者之间**不能留空隙,也不能插入任何音符**: + +- 留空隙 → `400`,提示 `slur starts s after the previous note ends` +- 中间插 `sp` / `sil` → 同样是上面这个 `400`(`sp` 会被服务端剔除,等价于留空隙) +- 中间插 `br` → `400`,提示 `slur must follow a general/slur note` +- `slur` 也不能是 notes 里的第一个音符 → `400` `first note can not be slur` + +需要在拖腔之后断开,请把 `sp` / `br` 放在 `slur` **结束之后**,例如 +`general[0,1] + slur[1,2] + br[2,2.5]`。 + +> 普通(非 slur)音符之间不需要显式 `sp`,直接留时间空隙即可,引擎会自动补静音,上限见 5.10。 +### 5.7 每个aces文件只能合成不大于90s的notes列表 +按 notes 的实际时间跨度(最大 `end_time` 减最小 `start_time`)计算,超出返回 `400`。 + +> 引擎按固定长度的画布推理,合成 5 秒与合成 90 秒消耗的算力基本相同, +> 因此**请尽量用一个文件表达一个完整段落**,不要人为切碎——切碎只会让同一段音频 +> 被反复推理,既拖慢速度又多消耗额度。 +> +> 另一条上限是**音素总数不超过 1200**(每个音符的音素数相加,呼吸音符与引擎自动补的 +> 静音各计 1)。演唱密度很高的内容(例如快速说唱)可能在时长还没到 90s 时先撞到它, +> 此时返回 `453` 并指出音素总数。 +### 5.8 必须提供speaker信息 +通过接口的 `speaker_id` 或 `mix_info` 参数提供,歌手需在歌手列表内。 +### 5.9 如果要使用piece_params,需要认真核对piece_params的时间范围不能超过note列表的时间范围 +超出范围的部分会被自动裁掉,不会报错。 +### 5.10 音符之间的静音间隔 +片内允许长静音(前奏留白、中间的器乐段都可以直接留空),只要整片跨度不超过 5.7 的 +90s 上限即可,无需为了跨过一段空白而拆片。普通音符之间不必显式写 `sp`,留时间空隙 +即可,引擎会自动补静音。 + +### 5.11 音符之间不能时间重叠 +服务会先把 notes 按 `start_time` 排序,再要求相邻音符满足 +`后一个的 start_time >= 前一个的 end_time`。重叠返回 `453`,并给出具体时间,例如 +`note at 0.8000s overlaps the previous note ending at 1.0000s`。 +该规则对 `general` / `slur` / `br` 一律生效(`sp` / `sil` 已被提前剔除,不参与)。 +notes 数组本身**不要求**按时间排序。 + +> 从 MIDI / DAW 导出时请注意关闭 legato、检查叠轨,这是该错误最常见的来源。 \ No newline at end of file diff --git a/docs/aces_file_en.md b/docs/aces_file_en.md index 9fe142e..8bf2364 100644 --- a/docs/aces_file_en.md +++ b/docs/aces_file_en.md @@ -28,8 +28,8 @@ | start_time | number | Yes | Note start time, in seconds | | end_time | number | Yes | Note end time, in seconds | | type | string | No, default is "general" | Note type, see Note Type Description | -| pitch | number | No | Pitch value, see Pitch Value Description | -| language | string | No, default is "ch" | Note language: Chinese "ch", English "en", Japanese "jp" | +| pitch | number | Required when type is "general" | Pitch value, see Pitch Value Description. Optional for "slur" (inherits the pitch of the note it extends); ignored for "br"/"sp" | +| language | string | No, defaults to ch | Note language, see 5.3 (`ch` Chinese / `jp` Japanese / `en` English / `spa` Spanish) | | phone | Array | No | List of phonemes for the current note, see Phoneme Description | | syllable | string | No | Syllable for the current note, see Syllable Description | @@ -53,7 +53,15 @@ to: https://github.com/timedomain-tech/ACE_phonemes ### 2.4 Syllable Description -Only can be used when language is Chinese or Japanese, syllable can be used instead of phoneme lists +**Only Chinese (`language` = `ch`) supports syllable input.** You may give either pinyin +(e.g. `"la"`, `"shan"`) or a single Chinese character (e.g. `"星"`); the service converts it to +phonemes automatically, so `phone` is not required. + +- If the character has multiple readings, or the pinyin is invalid, `400` is returned — use + pinyin or supply `phone` directly instead. +- **For other languages (English / Japanese / Spanish etc.) please supply the `phone` list.** + These languages have no syllable conversion; supplying only `syllable` yields a default + pronunciation rather than your intended lyric. *Example:* @@ -85,10 +93,22 @@ Only can be used when language is Chinese or Japanese, syllable can be used inst ## 3. PIECE_PARAMS: Segment Parameters (Experimental Feature) -| Field Name | Field Type | Required | Description | -|------------|------------|----------|--------------------| -| pitch | Object | No | See `pitch` object | -| energy | Object | No | See `param` object | +| Field Name | Field Type | Required | Description | +|------------|------------|----------|------------------------------------------| +| pitch | Object | No | See `pitch` object | +| energy | Object | No | See `param` object | +| air | Object | No | See `param` object, breathiness | +| falsetto | Object | No | See `param` object, falsetto amount | +| tension | Object | No | See `param` object, vocal cord tension | + +> Effective layers: the `user` / `delta` layers of `pitch`, and the `user` layer of +> `energy` / `air` / `falsetto` / `tension`. These five parameters and these layers are +> all there is; any other field returns `400` with an explanation. + +Ready-to-submit examples: + +- `examples/vibrato_example.aces` — the `user` + `delta` layers of `pitch` (vibrato) +- `examples/param_example.aces` — `user` curves for `energy` / `air` / `falsetto` / `tension` ### 3.1 PITCH: Pitch Representation @@ -97,6 +117,31 @@ Only can be used when language is Chinese or Japanese, syllable can be used inst | user | Array(PIECE_VALUE) | No | User-defined pitch curve, see `piece_value` object, value range 30-90 | | delta | Array(PIECE_VALUE) | No | User-defined pitch shift, see `piece_value` object, value range [-4,4] | +> **`delta` must be supplied together with `user`.** `delta` is an offset applied on top +> of the `user` pitch curve; if you supply `delta` without `user`, that layer is ignored +> (no error is returned, but effects such as vibrato will not take effect). +> Time ranges not covered by `user` are predicted by the model. + +> **Wherever `user` covers, it takes over the pitch completely**; only the uncovered +> ranges are left to the model. We therefore recommend letting `user` span the whole +> note: if it only covers the tail where the vibrato sits, the boundary between the +> model-predicted range and the `user` range produces a pitch step of nearly a +> semitone (about 100 cents, measured), which is audible as a sudden jump. + +> **Sustained notes already carry a model-generated natural vibrato** (measured at +> roughly 5–6 Hz with a depth of about ±0.5 semitones). You only need to supply +> `pitch` when you want precise control over when the vibrato starts and over its +> rate and depth; if you just want "some vibrato", leave `pitch` out. This also +> means that if you supply +> `delta` but forget `user`, the vibrato you hear is the model's default behaviour, +> not the curve you wrote. + +`examples/vibrato_example.aces` is a complete example: a single note from 4.75s to +6.00s, whose `user` layer lays down a flat pitch line at 63 across the whole note +using 25 points, while `delta` adds a 6 Hz sine starting at 5.29s — so it sounds +like a straight tone followed by vibrato. To make the effect obvious, that example +uses a vibrato depth of ±1.8 semitones; real singing is usually within ±0.3~0.7. + Example: ``` @@ -108,17 +153,27 @@ Example: ### 3.2 PARAM: Parameter Representation -| Field Name | Field Type | Required | Description | -|------------|---------------------|----------|-----------------------------------------------------------------------------------------| -| user | Array(PIECE_VALUE) | No | Custom parameter curve, see `piece_value` object, value range depends on parameter type | -| envelope | Array(PIECE_VALUE) | No | Parameter envelope curve, see `piece_value` object, value range 0-2 | +| Field Name | Field Type | Required | Description | +|------------|--------------------|----------|----------------------------------------------------------------| +| user | Array(PIECE_VALUE) | No | Custom parameter curve, see `piece_value` object, range 0~1 | + +**All four parameters use the same `user` range, 0~1.** A negative value means +"unspecified here, let the model predict it". Values outside 0~1 are clamped, not rejected. + +What the four parameters do perceptually: `energy` is loudness and intensity; `air` is +the amount of breath; `falsetto` is the falsetto ratio (it weakens the upper harmonics, +making the voice softer); `tension` is vocal cord tension (it raises brightness, and is +the most pronounced of the four). + +`examples/param_example.aces` demonstrates how to write these four curves: four +sustained notes at the same pitch, each carrying a low-to-high ramp on exactly one +parameter, so you can audition one parameter at a time. Example: ``` { - "user": [PIECE_VALUE], - "envelope": [PIECE_VALUE] + "user": [PIECE_VALUE] } ``` @@ -130,6 +185,17 @@ Example: | hop_time | number | Yes | The interval between every two consecutive data frames in the values array | | values | Array | Yes | An array of values with different ranges depending on the value type | +The time range covered by one segment is `start_time` through +`start_time + len(values) * hop_time`. + +> `values` is resampled to the engine's internal frame rate (about one frame every +> 5.8ms), so `hop_time` is yours to choose: a coarse grid (0.05s, say) is plenty for a +> slowly varying curve, and there is no need to supply one point per frame. Different +> layers under the same `piece_params` (for instance the `user` and `delta` layers of +> `pitch`) are not required to share a `hop_time` either — each layer is aligned to the +> timeline independently using its own `start_time` / `hop_time`, and the layers are +> then combined. + Example: ``` @@ -170,6 +236,11 @@ Description: PAD is additional information that is usually not required. When the ACES file is used for deep learning model synthesis of singing voices, the note information before and after this segment can be added to obtain better synthesis results. +> `pad` can be omitted entirely — the service fills it +> in from the first/last note times and aligns it to the internal frame grid. If you do supply +> `pad`, its `type` (`sp` / `br` / `sil` etc.) is honoured, but `start_time` / `end_time` are +> recomputed. The field name `pad_notes` is accepted as well. + Example: ``` @@ -183,13 +254,70 @@ Example: *definition:* Each note has only a unique vowel, and the consonants before the vowel in this note are called pre consonants. The consonants after the vowel in this note are called post consonants -### 5.1 Each note must have sufficient length, notes less than 0.02s have a high probability of experiencing synthesis anomalies +### 5.1 Each note must be long enough to hold its phonemes +The minimum unit of phoneme duration is one frame (about 5.8ms). **The number of phonemes in a +note must not exceed the number of frames its duration spans** — e.g. a 16ms note (about 2 frames) +can hold at most 2 phonemes; a third one returns `453` together with the timestamp of that note. +As a rule of thumb, keep each note at least 0.05s long. + ### 5.2 Pitch must be in the range of 30 to 90 -### 5.3 The language field only supports ch \ en \ jp \ spa +440Hz reference = 69. **This is the hard validation range**; values outside it return `453` +together with the offending note time. Out-of-range pitches are treated as an invalid +fundamental frequency inside the engine (you would hear silence or artefacts), so they are +rejected up front rather than passed through into unusable audio. +### 5.3 The language field supports ch / jp / en / spa +Chinese, Japanese, English and Spanish respectively. Any other value returns `400`. +Each language has its own table of legal phonemes, see 5.4. ### 5.4 Each note's phone must be legal, and the list of legal phones varies for different languages +See https://github.com/timedomain-tech/ACE_phonemes . For English, Japanese and Spanish you can +generate phones from lyrics with `api/demo/lyrics2phone.py`; see the README. +Phonemes outside the table return `453` +together with the unrecognised phoneme names. ### 5.5 Each note must have exactly one vowel -### 5.6 If you want to adjust consonant_time_head or consonant_time_tail, you need to carefully check: the number of pre consonants in this note is equal to the number of elements in consonant_time_head; The number of post_consonants in this note is equal to the number of elements in consonant_time_tail -### 5.7 Due to internal logic, we will merge the post_consonant of each note with the pre_consonant of the next note for calculation. Therefore, we need to ensure that the length of this note is greater than the consonant_time_tail length of this note plus the consonant_time_head length of the next note -### 5.8 Each aces file can only synthesize a notes list of no more than 18 seconds -### 5.9 Speaker information must be provided -### 5.10 If you want to use piece_params field, you need to carefully check that the time range of piece_params cannot exceed the time range of the note list \ No newline at end of file +A note without a vowel returns `453` (otherwise that syllable would be silent). +### 5.6 A `slur` must be exactly adjacent to the note it extends +`slur.start_time` must equal the `end_time` of the preceding sounding note (`general` or `slur`), +with a 1ms tolerance. **No gap may be left and no note may be inserted in between**: + +- Leaving a gap returns `400`: `slur starts s after the previous note ends` +- Inserting `sp` / `sil` returns the same `400` (`sp` is stripped by the server, which is + equivalent to leaving a gap) +- Inserting `br` returns `400`: `slur must follow a general/slur note` +- A `slur` also cannot be the first note in `notes` → `400` `first note can not be slur` + +To break the sound after a sustained note, place the `sp` / `br` **after** the `slur` ends, e.g. +`general[0,1] + slur[1,2] + br[2,2.5]`. + +> Between ordinary (non-slur) notes you do not need an explicit `sp` — just leave a time gap and +> the engine inserts the silence; see 5.10 for the limit. +### 5.7 Each aces file can only synthesize a notes list of no more than 90 seconds +Measured as the actual time span of the notes (largest `end_time` minus smallest `start_time`); +exceeding it returns `400`. + +> The engine infers on a fixed-length canvas, so synthesizing 5 seconds and 90 seconds cost +> about the same compute. **Prefer one file per complete passage** and do not slice content up: +> slicing only makes the same audio go through inference repeatedly, which is both slower and +> more expensive in credits. +> +> The other limit is a **total of 1200 phonemes** (summed over the notes, with breath notes and +> engine-inserted silences counting as 1 each). Very dense material (fast rap, for example) can +> hit this before reaching 90s; that returns `453` and reports the phoneme total. +### 5.8 Speaker information must be provided +Supply it via the `speaker_id` or `mix_info` request parameter; the singer must be in the singer list. +### 5.9 If you want to use piece_params field, you need to carefully check that the time range of piece_params cannot exceed the time range of the note list +Parts outside the range are clipped automatically without an error. +### 5.10 Silence between notes +Long silences inside a piece are fine (an intro rest or an instrumental section can simply be +left empty) as long as the whole span stays within the 90s limit of 5.7 — there is no need to +split a piece just to skip over a gap. Ordinary notes do not need an explicit `sp` between +them; leave a time gap and the engine fills in the silence. + +### 5.11 Notes must not overlap in time +The service first sorts the notes by `start_time`, then requires every adjacent pair to satisfy +`start_time of the later note >= end_time of the earlier note`. An overlap returns `453` with the +exact position, e.g. `note at 0.8000s overlaps the previous note ending at 1.0000s`. +The rule applies to `general` / `slur` / `br` alike (`sp` / `sil` are stripped beforehand and do +not participate). The notes array itself does **not** need to be sorted by time. + +> When exporting from MIDI or a DAW, turn off legato and check for stacked tracks — that is by +> far the most common cause of this error. \ No newline at end of file diff --git a/examples/param_example.aces b/examples/param_example.aces new file mode 100644 index 0000000..2a4396f --- /dev/null +++ b/examples/param_example.aces @@ -0,0 +1,239 @@ +{ + "notes": [ + { + "end_time": 2.5, + "language": "ch", + "pitch": 65, + "start_time": 0.5, + "syllable": "a", + "type": "general" + }, + { + "end_time": 4.8, + "language": "ch", + "pitch": 65, + "start_time": 2.8, + "syllable": "a", + "type": "general" + }, + { + "end_time": 7.1, + "language": "ch", + "pitch": 65, + "start_time": 5.1, + "syllable": "a", + "type": "general" + }, + { + "end_time": 9.4, + "language": "ch", + "pitch": 65, + "start_time": 7.4, + "syllable": "a", + "type": "general" + } + ], + "piece_params": { + "air": { + "user": [ + { + "hop_time": 0.05, + "start_time": 2.8, + "values": [ + 0.0, + 0.0256, + 0.0513, + 0.0769, + 0.1026, + 0.1282, + 0.1538, + 0.1795, + 0.2051, + 0.2308, + 0.2564, + 0.2821, + 0.3077, + 0.3333, + 0.359, + 0.3846, + 0.4103, + 0.4359, + 0.4615, + 0.4872, + 0.5128, + 0.5385, + 0.5641, + 0.5897, + 0.6154, + 0.641, + 0.6667, + 0.6923, + 0.7179, + 0.7436, + 0.7692, + 0.7949, + 0.8205, + 0.8462, + 0.8718, + 0.8974, + 0.9231, + 0.9487, + 0.9744, + 1.0 + ] + } + ] + }, + "energy": { + "user": [ + { + "hop_time": 0.05, + "start_time": 0.5, + "values": [ + 0.2885, + 0.3057, + 0.323, + 0.3402, + 0.3575, + 0.3748, + 0.392, + 0.4093, + 0.4265, + 0.4438, + 0.461, + 0.4783, + 0.4956, + 0.5128, + 0.5301, + 0.5473, + 0.5646, + 0.5818, + 0.5991, + 0.6164, + 0.6336, + 0.6509, + 0.6682, + 0.6854, + 0.7027, + 0.7199, + 0.7372, + 0.7544, + 0.7717, + 0.789, + 0.8062, + 0.8235, + 0.8407, + 0.858, + 0.8753, + 0.8925, + 0.9098, + 0.927, + 0.9443, + 0.9615 + ] + } + ] + }, + "falsetto": { + "user": [ + { + "hop_time": 0.05, + "start_time": 5.1, + "values": [ + 0.0, + 0.0256, + 0.0513, + 0.0769, + 0.1026, + 0.1282, + 0.1538, + 0.1795, + 0.2051, + 0.2308, + 0.2564, + 0.2821, + 0.3077, + 0.3333, + 0.359, + 0.3846, + 0.4103, + 0.4359, + 0.4615, + 0.4872, + 0.5128, + 0.5385, + 0.5641, + 0.5897, + 0.6154, + 0.641, + 0.6667, + 0.6923, + 0.7179, + 0.7436, + 0.7692, + 0.7949, + 0.8205, + 0.8462, + 0.8718, + 0.8974, + 0.9231, + 0.9487, + 0.9744, + 1.0 + ] + } + ] + }, + "tension": { + "user": [ + { + "hop_time": 0.05, + "start_time": 7.4, + "values": [ + 0.0, + 0.0256, + 0.0513, + 0.0769, + 0.1026, + 0.1282, + 0.1538, + 0.1795, + 0.2051, + 0.2308, + 0.2564, + 0.2821, + 0.3077, + 0.3333, + 0.359, + 0.3846, + 0.4103, + 0.4359, + 0.4615, + 0.4872, + 0.5128, + 0.5385, + 0.5641, + 0.5897, + 0.6154, + 0.641, + 0.6667, + 0.6923, + 0.7179, + 0.7436, + 0.7692, + 0.7949, + 0.8205, + 0.8462, + 0.8718, + 0.8974, + 0.9231, + 0.9487, + 0.9744, + 1.0 + ] + } + ] + } + }, + "version": 1.1 +} diff --git a/examples/vibrato_example.aces b/examples/vibrato_example.aces index 9a5a8ec..80e55bf 100644 --- a/examples/vibrato_example.aces +++ b/examples/vibrato_example.aces @@ -1,9 +1,6 @@ { "notes": [ { - "consonant_time_head": [ - 0.12708333333333321 - ], "end_time": 6, "language": "ch", "phone": [ @@ -149,6 +146,37 @@ } ], "user": [ + { + "start_time": 4.75, + "hop_time": 0.05, + "values": [ + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0, + 63.0 + ] + } ] } }, diff --git a/examples/xiaoxingxing.aces b/examples/xiaoxingxing.aces index acd5f20..499e5cf 100644 --- a/examples/xiaoxingxing.aces +++ b/examples/xiaoxingxing.aces @@ -1,794 +1,490 @@ -{ - "version": 1.1, - "pad_notes": { - "begin": { - "start_time": -1, - "end_time": 0, - "type": "sp" - }, - "end": { - "start_time": 5.53125, - "end_time": 6.53125, - "type": "sp" - } - }, - "notes": [ - { - "start_time": 0, - "end_time": 0.375, - "type": "general", - "language": "ch", - "pitch": 60, - "phone": [ - "y", - "i" - ] - }, - { - "start_time": 0.375, - "end_time": 0.75, - "type": "general", - "language": "ch", - "pitch": 60, - "phone": [ - "sh", - "an" - ] - }, - { - "start_time": 0.75, - "end_time": 1.125, - "type": "general", - "language": "ch", - "pitch": 67, - "phone": [ - "y", - "i" - ] - }, - { - "start_time": 1.125, - "end_time": 1.5, - "type": "general", - "language": "ch", - "pitch": 67, - "phone": [ - "sh", - "an" - ] - }, - { - "start_time": 1.5, - "end_time": 1.546875, - "type": "general", - "language": "ch", - "pitch": 67, - "phone": [ - "l", - "iang" - ] - }, - { - "start_time": 1.546875, - "end_time": 1.78125, - "type": "slur", - "language": "ch", - "pitch": 69, - "phone": [ - "l", - "iang" - ] - }, - { - "start_time": 1.875, - "end_time": 2.0625, - "type": "general", - "language": "ch", - "pitch": 69, - "phone": [ - "j", - "ing" - ] - }, - { - "start_time": 2.0625, - "end_time": 2.4375, - "type": "general", - "language": "ch", - "pitch": 67, - "phone": [ - "j", - "ing" - ] - }, - { - "start_time": 3, - "end_time": 3.375, - "type": "general", - "language": "ch", - "pitch": 65, - "phone": [ - "m", - "an" - ] - }, - { - "start_time": 3.375, - "end_time": 3.5625, - "type": "general", - "language": "ch", - "pitch": 65, - "phone": [ - "t", - "ian" - ] - }, - { - "start_time": 3.5625, - "end_time": 4.125, - "type": "general", - "language": "ch", - "pitch": 64, - "phone": [ - "d", - "ou" - ] - }, - { - "start_time": 4.125, - "end_time": 4.3125, - "type": "general", - "language": "ch", - "pitch": 64, - "phone": [ - "sh", - "iii" - ] - }, - { - "start_time": 4.3125, - "end_time": 4.5, - "type": "slur", - "language": "ch", - "pitch": 60, - "phone": [ - "sh", - "iii" - ] - }, - { - "start_time": 4.5, - "end_time": 4.546875, - "type": "general", - "language": "ch", - "pitch": 60, - "phone": [ - "x", - "iao" - ] - }, - { - "start_time": 4.546875, - "end_time": 4.875, - "type": "slur", - "language": "ch", - "pitch": 62, - "phone": [ - "x", - "iao" - ] - }, - { - "start_time": 4.875, - "end_time": 5.15625, - "type": "general", - "language": "ch", - "pitch": 62, - "phone": [ - "x", - "ing" - ] - }, - { - "start_time": 5.15625, - "end_time": 5.53125, - "type": "general", - "language": "ch", - "pitch": 60, - "phone": [ - "x", - "ing" - ] - } - ], - "piece_params": { - "air": { - "envelope": [], - "user": [] - }, - "energy": { - "envelope": [ - { - "hop_time": 0.019999999552965164, - "start_time": -0.07546485260770974, - "values": [ - 0.6959999799728394, - 0.6959999799728394, - 0.6959999799728394, - 0.6959999799728394, - 0.6790000200271606, - 0.7680000066757202, - 0.8280199766159058, - 0.8743025064468384, - 0.8946149349212646, - 0.913707435131073, - 0.9639999866485596, - 0.9639999866485596, - 0.9290000200271606, - 0.9053850769996643, - 0.8389999866485596, - 0.8389999866485596, - 0.8209999799728394, - 0.8036724328994751, - 0.7860000133514404, - 0.7860000133514404, - 0.8040000200271606, - 0.8209999799728394, - 0.8209999799728394, - 0.8391024470329285, - 0.8714149594306946, - 0.8930000066757202, - 0.911502480506897, - 0.9469074606895447, - 0.9645125269889832, - 0.9819999933242798, - 1.0342148542404175, - 1.0360000133514404, - 1.0360000133514404, - 1.0360000133514404, - 1.0360000133514404, - 1.0360000133514404, - 1.0180000066757202, - 1.0180000066757202, - 0.9744876623153687, - 0.9555851817131042, - 0.9197852611541748, - 0.9110000133514404, - 0.9001850485801697, - 0.8813851475715637, - 0.875, - 0.875, - 0.8569999933242798, - 0.8569999933242798, - 0.8389999866485596, - 0.8389999866485596, - 0.8389999866485596, - 0.8405075073242188, - 0.8569999933242798, - 0.8569999933242798, - 0.8569999933242798, - 0.8569999933242798, - 0.8389999866485596, - 0.8389999866485596, - 0.8389999866485596, - 0.8389999866485596, - 0.8324877023696899, - 0.8209999799728394, - 0.8209999799728394, - 0.8209999799728394, - 0.8209999799728394, - 0.8209999799728394, - 0.8209999799728394, - 0.8209999799728394, - 0.8228456974029541, - 0.8266827464103699, - 0.8209999799728394, - 0.8209999799728394, - 0.8389999866485596, - 0.875, - 0.9110000133514404, - 0.9290000200271606, - 0.9110000133514404, - 0.8678825497627258, - 0.8172826766967773, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.8040000200271606, - 0.8389999866485596, - 0.8924047946929932, - 0.9285171031951904, - 0.98260498046875, - 1.0180000066757202, - 1.044414758682251, - 1.0540000200271606, - 1.0540000200271606, - 1.0540000200271606, - 1.02028226852417, - 0.9696827530860901, - 0.8555125594139099, - 0.8760048747062683, - 0.9103025197982788, - 0.9110000133514404, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.8930000066757202, - 0.875, - 0.8389999866485596, - 0.8389999866485596, - 0.8024882078170776, - 0.7680000066757202, - 0.7324882745742798, - 0.6959999799728394, - 0.6764883995056152, - 0.6224875450134277, - 0.5683854818344116, - 0.5181950926780701, - 0.46399998664855957, - 0.42298534512519836, - 0.40448814630508423, - 0.3930000066757202, - 0.3930000066757202, - 0.41100001335144043, - 0.41100001335144043, - 0.4516047537326813, - 0.4819999933242798, - 0.5237070322036743, - 0.5787263512611389, - 0.625, - 0.6883273720741272, - 0.729307234287262, - 0.7680000066757202, - 0.836428701877594, - 0.9090097546577454, - 0.9633023738861084, - 0.9639999866485596, - 0.9639999866485596, - 0.9298928380012512, - 0.8539860844612122, - 0.76348876953125, - 0.6959999799728394, - 0.6959999799728394, - 0.6544880270957947, - 0.6430000066757202, - 0.6430000066757202, - 0.6430000066757202, - 0.6704140901565552, - 0.6959999799728394, - 0.761013925075531, - 0.8040000200271606, - 0.8389999866485596, - 0.8705117106437683, - 0.8930000066757202, - 0.9110000133514404, - 0.9110000133514404, - 0.9293072819709778, - 0.9639999866485596, - 0.9639999866485596, - 0.9639999866485596, - 0.9639999866485596, - 0.9639999866485596, - 0.96058589220047, - 0.9110000133514404, - 0.8569999933242798, - 0.8389999866485596, - 0.8040000200271606, - 0.8040000200271606, - 0.8209999799728394, - 0.8485122323036194, - 0.8930000066757202, - 0.8930000066757202, - 0.8569999933242798, - 0.8267856240272522, - 0.8040000200271606, - 0.8040000200271606, - 0.7860000133514404, - 0.7860000133514404, - 0.7860000133514404, - 0.7860000133514404, - 0.8203024864196777, - 0.8225127458572388, - 0.8389999866485596, - 0.8418095707893372, - 0.8569999933242798, - 0.8930000066757202, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.875, - 0.8569999933242798, - 0.8389999866485596, - 0.8389999866485596, - 0.8209999799728394, - 0.8209999799728394, - 0.7860000133514404, - 0.7860000133514404, - 0.7860000133514404, - 0.7860000133514404, - 0.7860000133514404, - 0.8195073008537292, - 0.8389999866485596, - 0.8416152596473694, - 0.8928046822547913, - 0.9110000133514404, - 0.915512204170227, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.9110000133514404, - 0.8807841539382935, - 0.8444877862930298, - 0.8081850409507751, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7846049666404724, - 0.8209999799728394, - 0.8386093378067017, - 0.8564047813415527, - 0.9110000133514404, - 0.9110000133514404, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9290000200271606, - 0.9110000133514404, - 0.9110000133514404, - 0.8930000066757202, - 0.860892117023468, - 0.8389999866485596, - 0.8389999866485596, - 0.8209999799728394, - 0.7860000133514404, - 0.7788920998573303, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.7680000066757202, - 0.75, - 0.75, - 0.7364863157272339, - 0.7139999866485596, - 0.6959999799728394, - 0.688892126083374, - 0.6610000133514404, - 0.6263949871063232, - 0.625, - 0.606097400188446 - ] - } - ], - "user": [ - ] - }, - "falsetto": { - "envelope": [], - "user": [] - }, - "pitch": { - "user": [ - { - "hop_time": 0.019999999552965164, - "start_time": -0.07546485260770974, - "values": [ - 59.9109992980957, - 59.9109992980957, - 59.9109992980957, - 59.9109992980957, - 59.8849983215332, - 59.80400085449219, - 59.80651092529297, - 59.97286605834961, - 60.04804992675781, - 60.0989990234375, - 60.01900100708008, - 59.9379997253418, - 59.9109992980957, - 59.858001708984375, - 59.858001708984375, - 59.95275115966797, - 60.01900100708008, - 60.010833740234375 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 0.3845351473922903, - "values": [ - 60.473228454589844, - 60.391658782958984, - 60.0773811340332, - 60.03499984741211, - 60.01139450073242, - 59.962284088134766, - 59.921390533447266, - 59.879486083984375, - 59.8595085144043, - 59.94221115112305, - 60.00853729248047, - 60.08570861816406, - 60.125, - 60.196258544921875, - 60.05449676513672, - 59.8471794128418, - 60.207611083984375, - 62.161224365234375, - 64.33740234375, - 65.96430969238281, - 64.3043212890625, - 65.42056274414062, - 66.12921142578125, - 66.49454498291016, - 66.6357192993164, - 66.75171661376953, - 66.86982727050781, - 67.05062103271484, - 67.12600708007812, - 66.97689819335938, - 66.13201141357422, - 64.08970642089844 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 1.1245351473922902, - "values": [ - 65.1421127319336, - 66.59262084960938, - 67.1240234375, - 67.07817840576172, - 67.00048828125, - 66.94629669189453, - 66.97650909423828, - 67.0398178100586, - 67.06310272216797, - 67.06900024414062, - 67.08200073242188, - 67.08650207519531, - 67.10990905761719, - 67.1259994506836, - 67.08158111572266, - 66.90695190429688, - 66.62844848632812, - 66.21713256835938, - 66.01158905029297, - 66.4884033203125, - 67.20635223388672, - 67.76579284667969, - 68.333251953125, - 68.7494888305664, - 68.9266357421875, - 68.98140716552734, - 69.0420150756836, - 69.11811828613281, - 69.14269256591797, - 69.12100219726562, - 69.1571044921875, - 69.15599060058594, - 69.09603881835938, - 68.96814727783203, - 67.86774444580078 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 1.8845351473922902, - "values": [ - 68.62303924560547, - 68.90890502929688, - 69.0770034790039, - 68.99646759033203, - 68.75907135009766, - 68.50521087646484, - 67.25048065185547, - 64.95246124267578, - 64.320068359375, - 64.9400634765625, - 66.2397232055664, - 66.84027862548828, - 66.93629455566406, - 66.84447479248047, - 66.72409057617188, - 66.92066192626953, - 67.1456298828125, - 67.20468139648438, - 66.98542785644531, - 66.67374420166016, - 66.52589416503906, - 66.61173248291016, - 66.84285736083984, - 67.1220474243164, - 67.13139343261719, - 66.96595001220703, - 66.59085083007812, - 66.30850219726562 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 2.9845351473922905, - "values": [ - 61.71379470825195, - 63.750999450683594, - 63.82394027709961, - 63.98801803588867, - 64.14214324951172, - 64.34117126464844, - 64.52960968017578, - 64.74411010742188, - 64.91304016113281, - 65.04000091552734, - 65.06700134277344, - 65.09400177001953, - 65.09400177001953, - 65.09400177001953, - 65.04000091552734, - 65.01300048828125, - 65.01300048828125, - 65.05623626708984 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 3.38453514739229, - "values": [ - 65.40715026855469, - 65.1338882446289, - 65.20038604736328, - 65.02045440673828, - 64.86554718017578, - 64.82353973388672, - 64.8958740234375, - 64.66828155517578, - 63.81052780151367, - 63.834442138671875, - 64.01040649414062, - 64.10295104980469, - 63.96178436279297, - 63.85900115966797, - 63.8353271484375, - 63.96876907348633, - 64.0999984741211, - 64.0999984741211, - 64.01899719238281, - 63.832000732421875, - 63.832000732421875, - 63.901241302490234, - 64.18099975585938, - 64.18099975585938, - 64.03465270996094, - 63.88600158691406, - 63.85900115966797, - 63.98496627807617, - 64.0739974975586 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 4.12453514739229, - "values": [ - 52.223304748535156, - 64.09255981445312, - 64.31160736083984, - 64.13105010986328, - 63.909366607666016, - 63.83240509033203, - 63.9665412902832, - 64.13261413574219, - 63.99195861816406, - 63.280887603759766, - 61.70742416381836, - 60.33191680908203, - 59.862953186035156, - 60.05305099487305, - 60.09475326538086, - 58.03649139404297, - 57.54496765136719 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 4.50453514739229, - "values": [ - 60.94289016723633, - 60.195892333984375, - 60.365257263183594, - 61.17446517944336, - 61.592586517333984, - 61.81834030151367, - 61.963130950927734, - 62.087501525878906, - 62.08100128173828, - 62.084999084472656, - 62.124412536621094, - 62.137298583984375, - 62.141963958740234, - 61.42073059082031, - 59.359840393066406 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 4.88453514739229, - "values": [ - 62.033973693847656, - 62.07883071899414, - 62.03389358520508, - 61.92497253417969, - 61.93690872192383, - 61.93620681762695, - 61.93009567260742, - 61.59451675415039, - 59.4375 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 5.16453514739229, - "values": [ - 59.58887481689453, - 59.94672775268555, - 60.105224609375, - 60.100486755371094, - 59.932952880859375, - 59.77146530151367, - 59.69420623779297, - 59.81044006347656, - 60.033729553222656, - 60.10900115966797, - 60.05430221557617, - 60.034385681152344, - 60.01161193847656, - 60.09199905395508, - 59.99445343017578, - 59.38619613647461, - 58.175567626953125, - 52.7064323425293 - ] - } - ] - }, - "tension": { - "envelope": [], - "user": [] - } - }, - "random_seed": 0 +{ + "version": 1.1, + "pad_notes": { + "begin": { + "start_time": -1, + "end_time": 0, + "type": "sp" + }, + "end": { + "start_time": 5.53125, + "end_time": 6.53125, + "type": "sp" + } + }, + "notes": [ + { + "start_time": 0, + "end_time": 0.375, + "type": "general", + "language": "ch", + "pitch": 60, + "phone": [ + "y", + "i" + ] + }, + { + "start_time": 0.375, + "end_time": 0.75, + "type": "general", + "language": "ch", + "pitch": 60, + "phone": [ + "sh", + "an" + ] + }, + { + "start_time": 0.75, + "end_time": 1.125, + "type": "general", + "language": "ch", + "pitch": 67, + "phone": [ + "y", + "i" + ] + }, + { + "start_time": 1.125, + "end_time": 1.5, + "type": "general", + "language": "ch", + "pitch": 67, + "phone": [ + "sh", + "an" + ] + }, + { + "start_time": 1.5, + "end_time": 1.546875, + "type": "general", + "language": "ch", + "pitch": 67, + "phone": [ + "l", + "iang" + ] + }, + { + "start_time": 1.546875, + "end_time": 1.78125, + "type": "slur", + "language": "ch", + "pitch": 69, + "phone": [ + "l", + "iang" + ] + }, + { + "start_time": 1.875, + "end_time": 2.0625, + "type": "general", + "language": "ch", + "pitch": 69, + "phone": [ + "j", + "ing" + ] + }, + { + "start_time": 2.0625, + "end_time": 2.4375, + "type": "general", + "language": "ch", + "pitch": 67, + "phone": [ + "j", + "ing" + ] + }, + { + "start_time": 3, + "end_time": 3.375, + "type": "general", + "language": "ch", + "pitch": 65, + "phone": [ + "m", + "an" + ] + }, + { + "start_time": 3.375, + "end_time": 3.5625, + "type": "general", + "language": "ch", + "pitch": 65, + "phone": [ + "t", + "ian" + ] + }, + { + "start_time": 3.5625, + "end_time": 4.125, + "type": "general", + "language": "ch", + "pitch": 64, + "phone": [ + "d", + "ou" + ] + }, + { + "start_time": 4.125, + "end_time": 4.3125, + "type": "general", + "language": "ch", + "pitch": 64, + "phone": [ + "sh", + "iii" + ] + }, + { + "start_time": 4.3125, + "end_time": 4.5, + "type": "slur", + "language": "ch", + "pitch": 60, + "phone": [ + "sh", + "iii" + ] + }, + { + "start_time": 4.5, + "end_time": 4.546875, + "type": "general", + "language": "ch", + "pitch": 60, + "phone": [ + "x", + "iao" + ] + }, + { + "start_time": 4.546875, + "end_time": 4.875, + "type": "slur", + "language": "ch", + "pitch": 62, + "phone": [ + "x", + "iao" + ] + }, + { + "start_time": 4.875, + "end_time": 5.15625, + "type": "general", + "language": "ch", + "pitch": 62, + "phone": [ + "x", + "ing" + ] + }, + { + "start_time": 5.15625, + "end_time": 5.53125, + "type": "general", + "language": "ch", + "pitch": 60, + "phone": [ + "x", + "ing" + ] + } + ], + "piece_params": { + "pitch": { + "user": [ + { + "hop_time": 0.019999999552965164, + "start_time": -0.07546485260770974, + "values": [ + 59.9109992980957, + 59.9109992980957, + 59.9109992980957, + 59.9109992980957, + 59.8849983215332, + 59.80400085449219, + 59.80651092529297, + 59.97286605834961, + 60.04804992675781, + 60.0989990234375, + 60.01900100708008, + 59.9379997253418, + 59.9109992980957, + 59.858001708984375, + 59.858001708984375, + 59.95275115966797, + 60.01900100708008, + 60.010833740234375 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 0.3845351473922903, + "values": [ + 60.473228454589844, + 60.391658782958984, + 60.0773811340332, + 60.03499984741211, + 60.01139450073242, + 59.962284088134766, + 59.921390533447266, + 59.879486083984375, + 59.8595085144043, + 59.94221115112305, + 60.00853729248047, + 60.08570861816406, + 60.125, + 60.196258544921875, + 60.05449676513672, + 59.8471794128418, + 60.207611083984375, + 62.161224365234375, + 64.33740234375, + 65.96430969238281, + 64.3043212890625, + 65.42056274414062, + 66.12921142578125, + 66.49454498291016, + 66.6357192993164, + 66.75171661376953, + 66.86982727050781, + 67.05062103271484, + 67.12600708007812, + 66.97689819335938, + 66.13201141357422, + 64.08970642089844 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 1.1245351473922902, + "values": [ + 65.1421127319336, + 66.59262084960938, + 67.1240234375, + 67.07817840576172, + 67.00048828125, + 66.94629669189453, + 66.97650909423828, + 67.0398178100586, + 67.06310272216797, + 67.06900024414062, + 67.08200073242188, + 67.08650207519531, + 67.10990905761719, + 67.1259994506836, + 67.08158111572266, + 66.90695190429688, + 66.62844848632812, + 66.21713256835938, + 66.01158905029297, + 66.4884033203125, + 67.20635223388672, + 67.76579284667969, + 68.333251953125, + 68.7494888305664, + 68.9266357421875, + 68.98140716552734, + 69.0420150756836, + 69.11811828613281, + 69.14269256591797, + 69.12100219726562, + 69.1571044921875, + 69.15599060058594, + 69.09603881835938, + 68.96814727783203, + 67.86774444580078 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 1.8845351473922902, + "values": [ + 68.62303924560547, + 68.90890502929688, + 69.0770034790039, + 68.99646759033203, + 68.75907135009766, + 68.50521087646484, + 67.25048065185547, + 64.95246124267578, + 64.320068359375, + 64.9400634765625, + 66.2397232055664, + 66.84027862548828, + 66.93629455566406, + 66.84447479248047, + 66.72409057617188, + 66.92066192626953, + 67.1456298828125, + 67.20468139648438, + 66.98542785644531, + 66.67374420166016, + 66.52589416503906, + 66.61173248291016, + 66.84285736083984, + 67.1220474243164, + 67.13139343261719, + 66.96595001220703, + 66.59085083007812, + 66.30850219726562 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 2.9845351473922905, + "values": [ + 61.71379470825195, + 63.750999450683594, + 63.82394027709961, + 63.98801803588867, + 64.14214324951172, + 64.34117126464844, + 64.52960968017578, + 64.74411010742188, + 64.91304016113281, + 65.04000091552734, + 65.06700134277344, + 65.09400177001953, + 65.09400177001953, + 65.09400177001953, + 65.04000091552734, + 65.01300048828125, + 65.01300048828125, + 65.05623626708984 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 3.38453514739229, + "values": [ + 65.40715026855469, + 65.1338882446289, + 65.20038604736328, + 65.02045440673828, + 64.86554718017578, + 64.82353973388672, + 64.8958740234375, + 64.66828155517578, + 63.81052780151367, + 63.834442138671875, + 64.01040649414062, + 64.10295104980469, + 63.96178436279297, + 63.85900115966797, + 63.8353271484375, + 63.96876907348633, + 64.0999984741211, + 64.0999984741211, + 64.01899719238281, + 63.832000732421875, + 63.832000732421875, + 63.901241302490234, + 64.18099975585938, + 64.18099975585938, + 64.03465270996094, + 63.88600158691406, + 63.85900115966797, + 63.98496627807617, + 64.0739974975586 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 4.12453514739229, + "values": [ + 52.223304748535156, + 64.09255981445312, + 64.31160736083984, + 64.13105010986328, + 63.909366607666016, + 63.83240509033203, + 63.9665412902832, + 64.13261413574219, + 63.99195861816406, + 63.280887603759766, + 61.70742416381836, + 60.33191680908203, + 59.862953186035156, + 60.05305099487305, + 60.09475326538086, + 58.03649139404297, + 57.54496765136719 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 4.50453514739229, + "values": [ + 60.94289016723633, + 60.195892333984375, + 60.365257263183594, + 61.17446517944336, + 61.592586517333984, + 61.81834030151367, + 61.963130950927734, + 62.087501525878906, + 62.08100128173828, + 62.084999084472656, + 62.124412536621094, + 62.137298583984375, + 62.141963958740234, + 61.42073059082031, + 59.359840393066406 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 4.88453514739229, + "values": [ + 62.033973693847656, + 62.07883071899414, + 62.03389358520508, + 61.92497253417969, + 61.93690872192383, + 61.93620681762695, + 61.93009567260742, + 61.59451675415039, + 59.4375 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 5.16453514739229, + "values": [ + 59.58887481689453, + 59.94672775268555, + 60.105224609375, + 60.100486755371094, + 59.932952880859375, + 59.77146530151367, + 59.69420623779297, + 59.81044006347656, + 60.033729553222656, + 60.10900115966797, + 60.05430221557617, + 60.034385681152344, + 60.01161193847656, + 60.09199905395508, + 59.99445343017578, + 59.38619613647461, + 58.175567626953125, + 52.7064323425293 + ] + } + ] + } + }, + "random_seed": 0 } \ No newline at end of file diff --git "a/examples/\343\201\257\343\202\213\343\202\222\343\201\202\343\201\204 cl \343\201\231\343\202\213\343\201\262\343\201\250_2b.aces" "b/examples/\343\201\257\343\202\213\343\202\222\343\201\202\343\201\204 cl \343\201\231\343\202\213\343\201\262\343\201\250_2b.aces" index 6fb5a6e..45638aa 100644 --- "a/examples/\343\201\257\343\202\213\343\202\222\343\201\202\343\201\204 cl \343\201\231\343\202\213\343\201\262\343\201\250_2b.aces" +++ "b/examples/\343\201\257\343\202\213\343\202\222\343\201\202\343\201\204 cl \343\201\231\343\202\213\343\201\262\343\201\250_2b.aces" @@ -1,1288 +1,740 @@ -{ - "version": 1.1, - "pad_notes": { - "begin": { - "start_time": 0, - "end_time": 0.6382978723404256, - "type": "sp" - }, - "end": { - "start_time": 10.212765957446809, - "end_time": 11.212765957446809, - "type": "sp" - } - }, - "notes": [ - { - "start_time": 0.6382978723404256, - "end_time": 0.7978723404255319, - "type": "general", - "language": "jp", - "pitch": 67, - "phone": [ - "h", - "a" - ] - }, - { - "start_time": 0.7978723404255319, - "end_time": 1.2765957446808511, - "type": "slur", - "language": "jp", - "pitch": 69, - "phone": [ - "h", - "a" - ] - }, - { - "start_time": 1.2765957446808511, - "end_time": 1.5957446808510638, - "type": "general", - "language": "jp", - "pitch": 69, - "phone": [ - "r", - "u" - ] - }, - { - "start_time": 1.5957446808510638, - "end_time": 1.9148936170212767, - "type": "general", - "language": "jp", - "pitch": 67, - "phone": [ - "w", - "o" - ] - }, - { - "start_time": 1.9148936170212767, - "end_time": 2.234042553191489, - "type": "general", - "language": "jp", - "pitch": 65, - "phone": [ - "a" - ] - }, - { - "start_time": 2.234042553191489, - "end_time": 2.3297872340425534, - "type": "general", - "language": "jp", - "pitch": 65, - "phone": [ - "i" - ] - }, - { - "start_time": 2.3297872340425534, - "end_time": 2.473404255319149, - "type": "slur", - "language": "jp", - "pitch": 67, - "phone": [ - "i" - ] - }, - { - "start_time": 2.473404255319149, - "end_time": 2.5531914893617023, - "type": "general", - "language": "jp", - "pitch": 67, - "phone": [ - "cl" - ] - }, - { - "start_time": 2.5531914893617023, - "end_time": 2.872340425531915, - "type": "general", - "language": "jp", - "pitch": 65, - "phone": [ - "s", - "u" - ] - }, - { - "start_time": 2.872340425531915, - "end_time": 3.1914893617021276, - "type": "general", - "language": "jp", - "pitch": 64, - "phone": [ - "r", - "u" - ] - }, - { - "start_time": 3.1914893617021276, - "end_time": 3.8297872340425534, - "type": "general", - "language": "jp", - "pitch": 62, - "phone": [ - "hy", - "i" - ] - }, - { - "start_time": 3.8297872340425534, - "end_time": 4.468085106382978, - "type": "general", - "language": "jp", - "pitch": 62, - "phone": [ - "t", - "o" - ] - }, - { - "start_time": 4.468085106382978, - "end_time": 5.1063829787234045, - "type": "general", - "language": "jp", - "pitch": 62, - "phone": [ - "h", - "a" - ] - }, - { - "start_time": 5.74468085106383, - "end_time": 5.908244680851064, - "type": "general", - "language": "jp", - "pitch": 69, - "phone": [ - "k", - "o" - ] - }, - { - "start_time": 5.908244680851064, - "end_time": 6.382978723404255, - "type": "slur", - "language": "jp", - "pitch": 70, - "phone": [ - "k", - "o" - ] - }, - { - "start_time": 6.382978723404255, - "end_time": 6.702127659574468, - "type": "general", - "language": "jp", - "pitch": 70, - "phone": [ - "k", - "o" - ] - }, - { - "start_time": 6.702127659574468, - "end_time": 7.0212765957446805, - "type": "general", - "language": "jp", - "pitch": 69, - "phone": [ - "r", - "o" - ] - }, - { - "start_time": 7.0212765957446805, - "end_time": 7.340425531914893, - "type": "general", - "language": "jp", - "pitch": 67, - "phone": [ - "ky", - "i" - ] - }, - { - "start_time": 7.340425531914893, - "end_time": 7.659574468085107, - "type": "general", - "language": "jp", - "pitch": 65, - "phone": [ - "y", - "o" - ] - }, - { - "start_time": 7.659574468085107, - "end_time": 7.9787234042553195, - "type": "general", - "language": "jp", - "pitch": 67, - "phone": [ - "ky", - "i" - ] - }, - { - "start_time": 7.9787234042553195, - "end_time": 8.297872340425531, - "type": "general", - "language": "jp", - "pitch": 70, - "phone": [ - "hy", - "i" - ] - }, - { - "start_time": 8.297872340425531, - "end_time": 10.212765957446809, - "type": "general", - "language": "jp", - "pitch": 69, - "phone": [ - "t", - "o" - ] - } - ], - "piece_params": { - "air": { - "envelope": [], - "user": [] - }, - "energy": { - "envelope": [ - { - "hop_time": 0.019999999552965164, - "start_time": 0.568888888888889, - "values": [ - 0.6700000166893005, - 0.6925333738327026, - 0.7089999914169312, - 0.7390000224113464, - 0.777999997138977, - 0.8180000185966492, - 0.8570888638496399, - 0.8769999742507935, - 0.8970000147819519, - 0.9359999895095825, - 0.9459999799728394, - 0.9459999799728394, - 0.9459999799728394, - 0.9407021403312683, - 0.9359999895095825, - 0.9359999895095825, - 0.9359999895095825, - 0.9359999895095825, - 0.9259999990463257, - 0.906000018119812, - 0.906000018119812, - 0.8970000147819519, - 0.8970000147819519, - 0.8769999742507935, - 0.8569999933242798, - 0.8180000185966492, - 0.8080000281333923, - 0.8080000281333923, - 0.8080000281333923, - 0.8080000281333923, - 0.8080000281333923, - 0.8180000185966492, - 0.8180000185966492, - 0.8376222848892212, - 0.8669999837875366, - 0.9278396368026733, - 0.9359999895095825, - 0.9359999895095825, - 0.9147021770477295, - 0.8870000243186951, - 0.8870000243186951, - 0.8669999837875366, - 0.8769999742507935, - 0.8733779191970825, - 0.8569999933242798, - 0.8569999933242798, - 0.8569999933242798, - 0.8569999933242798, - 0.8569999933242798, - 0.847000002861023, - 0.847000002861023, - 0.847000002861023, - 0.847000002861023, - 0.8569999933242798, - 0.8370000123977661, - 0.8219864964485168, - 0.8078667521476746, - 0.7879155278205872, - 0.7716268301010132, - 0.7590000033378601, - 0.7590000033378601, - 0.7587555050849915, - 0.7390000224113464, - 0.7390000224113464, - 0.718999981880188, - 0.699999988079071, - 0.6700000166893005, - 0.6600000262260437, - 0.6600000262260437, - 0.6600000262260437, - 0.6696045398712158, - 0.6912888288497925, - 0.7071059346199036, - 0.7390000224113464, - 0.7490000128746033, - 0.777999997138977, - 0.7979999780654907, - 0.8370000123977661, - 0.8370000123977661, - 0.847000002861023, - 0.847000002861023, - 0.8512659072875977, - 0.8569999933242798, - 0.8569999933242798, - 0.8669999837875366, - 0.8669999837875366, - 0.8569999933242798, - 0.8569999933242798, - 0.8180000185966492, - 0.7843561768531799, - 0.6815735101699829, - 0.5075554251670837, - 0.40400001406669617, - 0.4301464855670929, - 0.5019999742507935, - 0.6532742977142334, - 0.7879999876022339, - 0.809103786945343, - 0.6635832786560059, - 0.6600000262260437, - 0.6499999761581421, - 0.6625334620475769, - 0.718999981880188, - 0.777999997138977, - 0.7979999780654907, - 0.7979999780654907, - 0.7979999780654907, - 0.7916625738143921, - 0.7879999876022339, - 0.7571772336959839, - 0.7289999723434448, - 0.7390000224113464, - 0.7680000066757202, - 0.7883244752883911, - 0.8080000281333923, - 0.8080000281333923, - 0.7879999876022339, - 0.7441250085830688, - 0.7390000224113464, - 0.7390000224113464, - 0.7217702865600586, - 0.7089999914169312, - 0.7154765725135803, - 0.7289999723434448, - 0.7289999723434448, - 0.7289999723434448, - 0.718999981880188, - 0.718999981880188, - 0.6796054840087891, - 0.6499999761581421, - 0.6259779930114746, - 0.6209999918937683, - 0.6843379139900208, - 0.746621310710907, - 0.8368215560913086, - 0.8888183832168579, - 0.9502218961715698, - 0.9660000205039978, - 0.9373511075973511, - 0.8945420384407043, - 0.8569999933242798, - 0.7812226414680481, - 0.7385156154632568, - 0.6899999976158142, - 0.6557421684265137, - 0.6227914690971375, - 0.6024228930473328, - 0.5809999704360962, - 0.558102548122406, - 0.5419999957084656, - 0.5120000243186951, - 0.4887775778770447, - 0.46299999952316284, - 0.4473027288913727, - 0.4429999887943268, - 0.4429999887943268, - 0.46299999952316284, - 0.49300000071525574, - 0.5104970335960388, - 0.5314843654632568, - 0.5049999952316284, - 0.5687331557273865, - 0.6164221167564392, - 0.700244665145874, - 0.7825514674186707, - 0.8698300719261169, - 0.9340000152587891, - 0.9000625014305115, - 0.8569511771202087, - 0.8240000009536743, - 0.7837702631950378, - 0.7543557286262512, - 0.7199469804763794, - 0.7129999995231628, - 0.6934711933135986, - 0.6793705821037292, - 0.6503115296363831, - 0.6303471922874451, - 0.6230000257492065, - 0.6019999980926514, - 0.6019999980926514, - 0.5879999995231628, - 0.546999990940094, - 0.5400000214576721, - 0.5260000228881836, - 0.5189999938011169, - 0.5329999923706055, - 0.546999990940094, - 0.5540000200271606, - 0.5540000200271606, - 0.5566172003746033, - 0.5678886771202087, - 0.5779687762260437, - 0.6010976433753967, - 0.6525156497955322, - 0.6990000009536743, - 0.7540000081062317, - 0.7680000066757202, - 0.7820000052452087, - 0.7820000052452087, - 0.7820000052452087, - 0.7751557230949402, - 0.7680000066757202, - 0.7609999775886536, - 0.7400000095367432, - 0.7129999995231628, - 0.6879116296768188, - 0.6539157629013062, - 0.6137514710426331, - 0.5786718726158142, - 0.553591787815094, - 0.5189999938011169, - 0.472715824842453, - 0.4527041018009186, - 0.4275439381599426, - 0.3995959460735321, - 0.37266772985458374, - 0.3529999852180481, - 0.34047558903694153, - 0.32787108421325684, - 0.32499998807907104, - 0.3233557343482971, - 0.3109999895095825, - 0.3109999895095825, - 0.3109999895095825, - 0.3109999895095825, - 0.3109999895095825, - 0.31388428807258606, - 0.32499998807907104, - 0.34596410393714905, - 0.3650124669075012, - 0.3880000114440918, - 0.4120842218399048, - 0.40799999237060547, - 0.40916407108306885, - 0.421999990940094, - 0.422244131565094, - 0.42899999022483826, - 0.4359999895095825, - 0.4393640160560608, - 0.4429999887943268, - 0.4569999873638153, - 0.46399998664855957, - 0.4765239357948303, - 0.489564448595047, - 0.5022090077400208, - 0.5146445035934448, - 0.5206841230392456, - 0.5397241115570068, - 0.546999990940094, - 0.546999990940094, - 0.546999990940094, - 0.546999990940094, - 0.5419999957084656, - 0.5518574118614197, - 0.559008777141571, - 0.5619999766349792, - 0.5810839533805847, - 0.6010000109672546, - 0.6110000014305115, - 0.6284081935882568, - 0.6502441167831421, - 0.6702842116355896, - 0.699999988079071, - 0.7017285227775574, - 0.7289999723434448, - 0.7390000224113464, - 0.7509355545043945, - 0.7765239477157593, - 0.7979999780654907, - 0.8180000185966492, - 0.828000009059906, - 0.8569999933242798, - 0.8669999837875366, - 0.8769999742507935, - 0.8964126110076904, - 0.9036884903907776, - 0.9160000085830688, - 0.9160000085830688, - 0.8970000147819519, - 0.8970000147819519, - 0.8970000147819519, - 0.8870000243186951, - 0.8769999742507935, - 0.8569999933242798, - 0.8180000185966492, - 0.828000009059906, - 0.7979999780654907, - 0.777999997138977, - 0.7335429787635803, - 0.699999988079071, - 0.6700000166893005, - 0.6600000262260437, - 0.6600000262260437, - 0.6237441301345825, - 0.6110000014305115, - 0.5913555026054382, - 0.5809999704360962, - 0.5809999704360962, - 0.5809999704360962, - 0.5809999704360962, - 0.6010000109672546, - 0.6010000109672546, - 0.6209999918937683, - 0.6598930954933167, - 0.6600000262260437, - 0.6761757731437683, - 0.6900839805603027, - 0.7089999914169312, - 0.7753124833106995, - 0.8180000185966492, - 0.828000009059906, - 0.828000009059906, - 0.828000009059906, - 0.8225429654121399, - 0.7827656269073486, - 0.7367807626724243, - 0.6499999761581421, - 0.5959042906761169, - 0.5709999799728394, - 0.5709999799728394, - 0.5709999799728394, - 0.5733681917190552, - 0.5910000205039978, - 0.6255283355712891, - 0.6700000166893005, - 0.718999981880188, - 0.7555370926856995, - 0.8078486323356628, - 0.8180000185966492, - 0.8180000185966492, - 0.791824221611023, - 0.777999997138977, - 0.777999997138977, - 0.777999997138977, - 0.7621836066246033, - 0.7289999723434448, - 0.7289999723434448, - 0.718999981880188, - 0.718999981880188, - 0.7272129058837891, - 0.7390000224113464, - 0.777999997138977, - 0.7979999780654907, - 0.8080000281333923, - 0.8080000281333923, - 0.8080000281333923, - 0.7922636866569519, - 0.770379364490509, - 0.7590000033378601, - 0.7490000128746033, - 0.7390000224113464, - 0.7390000224113464, - 0.7390000224113464, - 0.7390000224113464, - 0.7490000128746033, - 0.7751318216323853, - 0.8073359131813049, - 0.828000009059906, - 0.8569999933242798, - 0.8738164305686951, - 0.8769999742507935, - 0.8748632669448853, - 0.8294062614440918, - 0.7915429472923279, - 0.7693828344345093, - 0.7390000224113464, - 0.699999988079071, - 0.673904299736023, - 0.6600000262260437, - 0.6600000262260437, - 0.6700000166893005, - 0.699999988079071, - 0.7276206016540527, - 0.7505292892456055, - 0.7812168002128601, - 0.8180000185966492, - 0.8569999933242798, - 0.8870000243186951, - 0.9217861294746399, - 0.9459999799728394, - 0.9730879068374634, - 0.9750000238418579, - 0.9725039005279541, - 0.9259999990463257, - 0.8767958879470825, - 0.8295107483863831, - 0.7958632707595825, - 0.777999997138977, - 0.737542986869812, - 0.6990000009536743, - 0.683224618434906, - 0.6570000052452087, - 0.6439999938011169, - 0.6299999952316284, - 0.6223959922790527, - 0.609000027179718, - 0.6026308536529541, - 0.6019999980926514, - 0.6019999980926514, - 0.6019999980926514, - 0.6019999980926514, - 0.6019999980926514, - 0.6019999980926514, - 0.6119287014007568, - 0.6210039258003235, - 0.6299999952316284, - 0.6401679515838623, - 0.6471240520477295, - 0.6561635732650757, - 0.681204080581665, - 0.6944892406463623, - 0.7185683846473694, - 0.7432968616485596, - 0.7703642845153809, - 0.7968086004257202, - 0.8240000009536743, - 0.8240000009536743, - 0.8240000009536743, - 0.8240000009536743, - 0.8240000009536743, - 0.8240000009536743, - 0.8240000009536743, - 0.8240000009536743, - 0.8240000009536743, - 0.7973915934562683, - 0.7873125076293945, - 0.7632314562797546, - 0.7401523590087891, - 0.7269999980926514, - 0.7129999995231628, - 0.6899560689926147, - 0.6718320250511169, - 0.6598759889602661, - 0.6548364162445068, - 0.634591817855835, - 0.6299999952316284, - 0.614431619644165, - 0.6043515801429749, - 0.6019999980926514, - 0.5925956964492798, - 0.5815561413764954, - 0.5740000009536743, - 0.5740000009536743, - 0.5554355382919312, - 0.546999990940094, - 0.5260000228881836, - 0.5206318497657776, - 0.5112758874893188, - 0.4934716820716858, - 0.48719578981399536, - 0.4779999852180481, - 0.4611157178878784, - 0.45307618379592896, - 0.4359999895095825, - 0.42399609088897705, - 0.40195605158805847, - 0.3848320245742798, - 0.38087597489356995, - 0.3728364109992981, - 0.36079591512680054, - 0.36000001430511475, - 0.3507158160209656, - 0.33899998664855957, - 0.33363574743270874, - 0.32499998807907104, - 0.32499998807907104, - 0.31700000166893005, - 0.31047606468200684, - 0.30399999022483826, - 0.30399999022483826, - 0.30399999022483826, - 0.30263182520866394, - 0.2980000078678131, - 0.2980000078678131, - 0.2980000078678131, - 0.2980000078678131, - 0.2980000078678131, - 0.2980000078678131, - 0.2980000078678131, - 0.2980000078678131, - 0.2980000078678131 - ] - } - ], - "user": [ - ] - }, - "falsetto": { - "envelope": [], - "user": [] - }, - "pitch": { - "enable_intervals": [ - { - "end_index": 127, - "start_index": 100 - }, - { - "end_index": 157, - "start_index": 131 - }, - { - "end_index": 190, - "start_index": 160 - }, - { - "end_index": 226, - "start_index": 195 - }, - { - "end_index": 285, - "start_index": 259 - }, - { - "end_index": 319, - "start_index": 291 - }, - { - "end_index": 349, - "start_index": 323 - }, - { - "end_index": 366, - "start_index": 355 - }, - { - "end_index": 381, - "start_index": 371 - }, - { - "end_index": 481, - "start_index": 387 - } - ], - "user": [ - { - "hop_time": 0.019999999552965164, - "start_time": 0.568888888888889, - "values": [ - 66.71700286865234, - 66.83089447021484, - 66.87000274658203, - 66.90799713134766, - 66.87000274658203, - 66.87000274658203, - 66.85066223144531, - 66.88629913330078, - 66.96566772460938, - 67.06204223632812, - 67.23104858398438, - 67.4423828125, - 67.68309020996094, - 68.04511260986328, - 68.38520050048828, - 68.67062377929688, - 68.91372680664062, - 69.07902526855469, - 69.11399841308594, - 69.00936889648438, - 68.9219970703125, - 68.90299987792969, - 68.90299987792969, - 68.94200134277344, - 69.05699920654297, - 69.09500122070312, - 69.09500122070312, - 69.09500122070312, - 68.9800033569336, - 68.88400268554688, - 68.84600067138672, - 68.19818878173828, - 68.4111557006836, - 69.04686737060547, - 69.13200378417969, - 69.08505249023438, - 69.02072143554688, - 68.96778869628906, - 68.96544647216797, - 68.98297119140625, - 68.99247741699219, - 68.9876937866211, - 69.02281188964844, - 69.06400299072266, - 69.00083923339844, - 68.80699920654297, - 68.80066680908203, - 68.96814727783203, - 69.08123016357422, - 69.0398178100586, - 68.75445556640625, - 68.058349609375, - 67.23480987548828, - 67.01300048828125, - 66.95800018310547, - 66.97161865234375, - 67.10931396484375, - 67.13600158691406, - 67.10430908203125, - 66.99859619140625, - 66.95800018310547, - 66.95800018310547, - 66.8770751953125, - 65.87834167480469, - 65.03032684326172, - 65.23722076416016, - 64.87187957763672, - 64.83137512207031, - 64.92121887207031, - 64.96604919433594, - 65.01016998291016, - 65.02876281738281, - 65.03380584716797, - 65.00331115722656, - 64.97911834716797, - 64.97415161132812, - 64.9659652709961, - 64.95599365234375, - 64.93604278564453, - 64.94566345214844, - 64.95487213134766, - 64.95999908447266, - 65.01300048828125, - 65.17430114746094, - 65.4743423461914, - 65.92937469482422, - 66.38731384277344, - 66.87273406982422, - 67.16172790527344, - 67.10848236083984, - 67.0653305053711, - 66.88032531738281, - 65.85107421875 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 2.5688888441854054, - "values": [ - 64.91033935546875, - 65.0093994140625, - 64.91587829589844, - 64.94184875488281, - 64.94996643066406, - 64.96499633789062, - 64.93186950683594, - 64.86957550048828, - 64.86375427246094, - 64.88949584960938, - 64.96722412109375, - 65.04122161865234, - 65.08100128173828, - 65.06683349609375, - 64.17918395996094, - 63.816314697265625, - 63.98831558227539, - 63.83770751953125, - 63.830135345458984, - 63.865692138671875, - 63.89281463623047, - 63.9835090637207, - 64.03305053710938, - 64.08061981201172, - 64.1314697265625, - 63.91684341430664, - 62.78413009643555, - 60.858970642089844 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 3.1888888303273255, - "values": [ - 59.448734283447266, - 61.75358200073242, - 62.14323425292969, - 62.14116668701172, - 62.045589447021484, - 61.977291107177734, - 61.95100784301758, - 61.929054260253906, - 61.85354232788086, - 61.832000732421875, - 61.84199905395508, - 61.87448501586914, - 61.89799880981445, - 61.876564025878906, - 61.85879135131836, - 61.85364532470703, - 61.8613166809082, - 61.84672546386719, - 61.85300064086914, - 61.86861038208008, - 61.88084411621094, - 61.89476776123047, - 61.9338493347168, - 61.98374938964844, - 62.00001907348633, - 61.99124526977539, - 59.784297943115234 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 3.7688888173633153, - "values": [ - 61.91600036621094, - 61.94300079345703, - 61.98500061035156, - 62.00027084350586, - 62.026363372802734, - 62.03900146484375, - 62.03900146484375, - 62.03900146484375, - 62.018951416015625, - 61.99800109863281, - 61.98439407348633, - 61.97100067138672, - 61.957000732421875, - 61.957000732421875, - 61.957000732421875, - 61.97100067138672, - 61.98500061035156, - 62.0341911315918, - 62.03900146484375, - 62.03900146484375, - 62.03900146484375, - 62.03900146484375, - 62.03900146484375, - 62.0260009765625, - 62.002506256103516, - 61.98918533325195, - 61.98500061035156, - 61.98500061035156, - 61.98500061035156, - 59.042179107666016, - 55.37126922607422 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 4.468888801717096, - "values": [ - 61.78262710571289, - 61.91457748413086, - 62.05268478393555, - 62.08000183105469, - 62.08000183105469, - 62.04495620727539, - 61.957157135009766, - 61.91600036621094, - 61.91600036621094, - 61.91600036621094, - 61.95705795288086, - 62.06700134277344, - 62.1425895690918, - 62.12200164794922, - 62.053001403808594, - 61.97895812988281, - 61.93453598022461, - 61.94612503051758, - 62.06700134277344, - 62.160457611083984, - 62.189998626708984, - 62.17822265625, - 62.07951736450195, - 61.94300079345703, - 61.888999938964844, - 61.91362380981445, - 62.021018981933594, - 62.1231575012207, - 62.20317459106445, - 62.21699905395508, - 62.06700134277344, - 61.957000732421875 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 5.7488887731068665, - "values": [ - 68.0863265991211, - 67.91714477539062, - 68.68708038330078, - 68.88613891601562, - 68.95032501220703, - 68.96652221679688, - 69.00247192382812, - 69.15177917480469, - 69.3249740600586, - 69.57121276855469, - 69.7752914428711, - 69.91744232177734, - 69.94971466064453, - 69.90763092041016, - 69.8875503540039, - 69.89399719238281, - 69.92741394042969, - 69.98353576660156, - 70.00888061523438, - 70.02300262451172, - 69.98914337158203, - 69.99401092529297, - 70.04017639160156, - 70.09317016601562, - 70.16637420654297, - 70.05557250976562, - 68.67967224121094 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 6.388888758801752, - "values": [ - 69.58097839355469, - 70.0131607055664, - 70.08633422851562, - 70.01130676269531, - 69.95957946777344, - 69.90242004394531, - 69.91836547851562, - 69.96061706542969, - 69.99176788330078, - 70.01341247558594, - 70.05168914794922, - 70.05534362792969, - 70.01415252685547, - 69.79425811767578, - 69.11405181884766, - 68.70128631591797, - 69.1116714477539, - 69.0816650390625, - 68.903564453125, - 68.90020751953125, - 68.91948699951172, - 68.95985412597656, - 69.01729583740234, - 69.04063415527344, - 69.06002044677734, - 69.1363296508789, - 69.0469970703125, - 67.43706512451172, - 64.90077209472656 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 7.028888744496637, - "values": [ - 66.73137664794922, - 66.90548706054688, - 67.0513687133789, - 67.02515411376953, - 66.9941177368164, - 66.97207641601562, - 67.0168228149414, - 67.06500244140625, - 67.11521911621094, - 67.1485824584961, - 66.99476623535156, - 66.589599609375, - 65.79695892333984, - 64.9925537109375, - 64.86425018310547, - 64.79849243164062, - 64.77163696289062, - 64.79842376708984, - 64.85647583007812, - 64.89299774169922, - 64.92061614990234, - 65.00763702392578, - 65.18905639648438, - 65.35322570800781, - 65.41242218017578, - 65.15796661376953, - 63.29110336303711 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 7.668888730191522, - "values": [ - 65.86260986328125, - 66.64722442626953, - 66.98595428466797, - 66.98262786865234, - 66.92767333984375, - 66.96661376953125, - 67.08795166015625, - 67.24669647216797, - 67.32135009765625, - 67.10350036621094, - 65.67154693603516, - 62.290382385253906 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 7.988888723038965, - "values": [ - 66.4554672241211, - 67.87029266357422, - 68.71417236328125, - 69.375, - 69.74347686767578, - 69.9512939453125, - 70.07365417480469, - 70.1510009765625, - 70.09028625488281, - 69.62267303466797, - 67.16484832763672 - ] - }, - { - "hop_time": 0.019999999552965164, - "start_time": 8.308888715886408, - "values": [ - 68.33386993408203, - 68.7474594116211, - 68.98222351074219, - 69.0422134399414, - 69.06800079345703, - 69.05315399169922, - 68.9998550415039, - 68.95999908447266, - 68.9209976196289, - 68.9010009765625, - 68.89199829101562, - 68.91100311279297, - 68.97976684570312, - 69.0244140625, - 69.0479965209961, - 69.0479965209961, - 69.02899932861328, - 69.0080337524414, - 68.9809799194336, - 68.93482208251953, - 68.91100311279297, - 68.9010009765625, - 68.9010009765625, - 68.91100311279297, - 68.94246673583984, - 68.94999694824219, - 68.94999694824219, - 68.94000244140625, - 68.92342376708984, - 68.97077178955078, - 69.02348327636719, - 69.08204650878906, - 69.08030700683594, - 69.01737976074219, - 68.90448760986328, - 68.851318359375, - 68.86917114257812, - 68.98340606689453, - 69.13423919677734, - 69.24153137207031, - 69.2308120727539, - 69.07621765136719, - 68.86053466796875, - 68.63694763183594, - 68.59126281738281, - 68.75342559814453, - 69.04985046386719, - 69.32463836669922, - 69.39779663085938, - 69.24708557128906, - 68.93489837646484, - 68.58981323242188, - 68.4312744140625, - 68.54347229003906, - 68.84676361083984, - 69.1526870727539, - 69.35409545898438, - 69.32548522949219, - 69.12592315673828, - 68.86775207519531, - 68.65621185302734, - 68.63607025146484, - 68.80140686035156, - 68.96906280517578, - 69.18865966796875, - 69.38107299804688, - 69.37968444824219, - 69.04866790771484, - 68.67898559570312, - 68.62699890136719, - 68.70750427246094, - 68.8719711303711, - 69.19197845458984, - 69.30702209472656, - 69.29537200927734, - 69.01058959960938, - 68.77043151855469, - 68.68181610107422, - 68.7058334350586, - 68.86554718017578, - 69.18499755859375, - 69.33514404296875, - 69.3357925415039, - 69.14891815185547, - 68.77117919921875, - 68.60831451416016, - 68.67807006835938, - 68.89498901367188, - 69.11416625976562, - 69.34343719482422, - 69.36011505126953, - 69.23129272460938, - 69.05360412597656, - 68.83795928955078, - 68.67695617675781 - ] - } - ] - }, - "tension": { - "envelope": [], - "user": [] - } - }, - "random_seed": 0 +{ + "version": 1.1, + "pad_notes": { + "begin": { + "start_time": 0, + "end_time": 0.6382978723404256, + "type": "sp" + }, + "end": { + "start_time": 10.212765957446809, + "end_time": 11.212765957446809, + "type": "sp" + } + }, + "notes": [ + { + "start_time": 0.6382978723404256, + "end_time": 0.7978723404255319, + "type": "general", + "language": "jp", + "pitch": 67, + "phone": [ + "h", + "a" + ] + }, + { + "start_time": 0.7978723404255319, + "end_time": 1.2765957446808511, + "type": "slur", + "language": "jp", + "pitch": 69, + "phone": [ + "h", + "a" + ] + }, + { + "start_time": 1.2765957446808511, + "end_time": 1.5957446808510638, + "type": "general", + "language": "jp", + "pitch": 69, + "phone": [ + "r", + "u" + ] + }, + { + "start_time": 1.5957446808510638, + "end_time": 1.9148936170212767, + "type": "general", + "language": "jp", + "pitch": 67, + "phone": [ + "w", + "o" + ] + }, + { + "start_time": 1.9148936170212767, + "end_time": 2.234042553191489, + "type": "general", + "language": "jp", + "pitch": 65, + "phone": [ + "a" + ] + }, + { + "start_time": 2.234042553191489, + "end_time": 2.3297872340425534, + "type": "general", + "language": "jp", + "pitch": 65, + "phone": [ + "i" + ] + }, + { + "start_time": 2.3297872340425534, + "end_time": 2.473404255319149, + "type": "slur", + "language": "jp", + "pitch": 67, + "phone": [ + "i" + ] + }, + { + "start_time": 2.473404255319149, + "end_time": 2.5531914893617023, + "type": "general", + "language": "jp", + "pitch": 67, + "phone": [ + "cl" + ] + }, + { + "start_time": 2.5531914893617023, + "end_time": 2.872340425531915, + "type": "general", + "language": "jp", + "pitch": 65, + "phone": [ + "s", + "u" + ] + }, + { + "start_time": 2.872340425531915, + "end_time": 3.1914893617021276, + "type": "general", + "language": "jp", + "pitch": 64, + "phone": [ + "r", + "u" + ] + }, + { + "start_time": 3.1914893617021276, + "end_time": 3.8297872340425534, + "type": "general", + "language": "jp", + "pitch": 62, + "phone": [ + "hy", + "i" + ] + }, + { + "start_time": 3.8297872340425534, + "end_time": 4.468085106382978, + "type": "general", + "language": "jp", + "pitch": 62, + "phone": [ + "t", + "o" + ] + }, + { + "start_time": 4.468085106382978, + "end_time": 5.1063829787234045, + "type": "general", + "language": "jp", + "pitch": 62, + "phone": [ + "h", + "a" + ] + }, + { + "start_time": 5.74468085106383, + "end_time": 5.908244680851064, + "type": "general", + "language": "jp", + "pitch": 69, + "phone": [ + "k", + "o" + ] + }, + { + "start_time": 5.908244680851064, + "end_time": 6.382978723404255, + "type": "slur", + "language": "jp", + "pitch": 70, + "phone": [ + "k", + "o" + ] + }, + { + "start_time": 6.382978723404255, + "end_time": 6.702127659574468, + "type": "general", + "language": "jp", + "pitch": 70, + "phone": [ + "k", + "o" + ] + }, + { + "start_time": 6.702127659574468, + "end_time": 7.0212765957446805, + "type": "general", + "language": "jp", + "pitch": 69, + "phone": [ + "r", + "o" + ] + }, + { + "start_time": 7.0212765957446805, + "end_time": 7.340425531914893, + "type": "general", + "language": "jp", + "pitch": 67, + "phone": [ + "ky", + "i" + ] + }, + { + "start_time": 7.340425531914893, + "end_time": 7.659574468085107, + "type": "general", + "language": "jp", + "pitch": 65, + "phone": [ + "y", + "o" + ] + }, + { + "start_time": 7.659574468085107, + "end_time": 7.9787234042553195, + "type": "general", + "language": "jp", + "pitch": 67, + "phone": [ + "ky", + "i" + ] + }, + { + "start_time": 7.9787234042553195, + "end_time": 8.297872340425531, + "type": "general", + "language": "jp", + "pitch": 70, + "phone": [ + "hy", + "i" + ] + }, + { + "start_time": 8.297872340425531, + "end_time": 10.212765957446809, + "type": "general", + "language": "jp", + "pitch": 69, + "phone": [ + "t", + "o" + ] + } + ], + "piece_params": { + "pitch": { + "user": [ + { + "hop_time": 0.019999999552965164, + "start_time": 0.568888888888889, + "values": [ + 66.71700286865234, + 66.83089447021484, + 66.87000274658203, + 66.90799713134766, + 66.87000274658203, + 66.87000274658203, + 66.85066223144531, + 66.88629913330078, + 66.96566772460938, + 67.06204223632812, + 67.23104858398438, + 67.4423828125, + 67.68309020996094, + 68.04511260986328, + 68.38520050048828, + 68.67062377929688, + 68.91372680664062, + 69.07902526855469, + 69.11399841308594, + 69.00936889648438, + 68.9219970703125, + 68.90299987792969, + 68.90299987792969, + 68.94200134277344, + 69.05699920654297, + 69.09500122070312, + 69.09500122070312, + 69.09500122070312, + 68.9800033569336, + 68.88400268554688, + 68.84600067138672, + 68.19818878173828, + 68.4111557006836, + 69.04686737060547, + 69.13200378417969, + 69.08505249023438, + 69.02072143554688, + 68.96778869628906, + 68.96544647216797, + 68.98297119140625, + 68.99247741699219, + 68.9876937866211, + 69.02281188964844, + 69.06400299072266, + 69.00083923339844, + 68.80699920654297, + 68.80066680908203, + 68.96814727783203, + 69.08123016357422, + 69.0398178100586, + 68.75445556640625, + 68.058349609375, + 67.23480987548828, + 67.01300048828125, + 66.95800018310547, + 66.97161865234375, + 67.10931396484375, + 67.13600158691406, + 67.10430908203125, + 66.99859619140625, + 66.95800018310547, + 66.95800018310547, + 66.8770751953125, + 65.87834167480469, + 65.03032684326172, + 65.23722076416016, + 64.87187957763672, + 64.83137512207031, + 64.92121887207031, + 64.96604919433594, + 65.01016998291016, + 65.02876281738281, + 65.03380584716797, + 65.00331115722656, + 64.97911834716797, + 64.97415161132812, + 64.9659652709961, + 64.95599365234375, + 64.93604278564453, + 64.94566345214844, + 64.95487213134766, + 64.95999908447266, + 65.01300048828125, + 65.17430114746094, + 65.4743423461914, + 65.92937469482422, + 66.38731384277344, + 66.87273406982422, + 67.16172790527344, + 67.10848236083984, + 67.0653305053711, + 66.88032531738281, + 65.85107421875 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 2.5688888441854054, + "values": [ + 64.91033935546875, + 65.0093994140625, + 64.91587829589844, + 64.94184875488281, + 64.94996643066406, + 64.96499633789062, + 64.93186950683594, + 64.86957550048828, + 64.86375427246094, + 64.88949584960938, + 64.96722412109375, + 65.04122161865234, + 65.08100128173828, + 65.06683349609375, + 64.17918395996094, + 63.816314697265625, + 63.98831558227539, + 63.83770751953125, + 63.830135345458984, + 63.865692138671875, + 63.89281463623047, + 63.9835090637207, + 64.03305053710938, + 64.08061981201172, + 64.1314697265625, + 63.91684341430664, + 62.78413009643555, + 60.858970642089844 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 3.1888888303273255, + "values": [ + 59.448734283447266, + 61.75358200073242, + 62.14323425292969, + 62.14116668701172, + 62.045589447021484, + 61.977291107177734, + 61.95100784301758, + 61.929054260253906, + 61.85354232788086, + 61.832000732421875, + 61.84199905395508, + 61.87448501586914, + 61.89799880981445, + 61.876564025878906, + 61.85879135131836, + 61.85364532470703, + 61.8613166809082, + 61.84672546386719, + 61.85300064086914, + 61.86861038208008, + 61.88084411621094, + 61.89476776123047, + 61.9338493347168, + 61.98374938964844, + 62.00001907348633, + 61.99124526977539, + 59.784297943115234 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 3.7688888173633153, + "values": [ + 61.91600036621094, + 61.94300079345703, + 61.98500061035156, + 62.00027084350586, + 62.026363372802734, + 62.03900146484375, + 62.03900146484375, + 62.03900146484375, + 62.018951416015625, + 61.99800109863281, + 61.98439407348633, + 61.97100067138672, + 61.957000732421875, + 61.957000732421875, + 61.957000732421875, + 61.97100067138672, + 61.98500061035156, + 62.0341911315918, + 62.03900146484375, + 62.03900146484375, + 62.03900146484375, + 62.03900146484375, + 62.03900146484375, + 62.0260009765625, + 62.002506256103516, + 61.98918533325195, + 61.98500061035156, + 61.98500061035156, + 61.98500061035156, + 59.042179107666016, + 55.37126922607422 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 4.468888801717096, + "values": [ + 61.78262710571289, + 61.91457748413086, + 62.05268478393555, + 62.08000183105469, + 62.08000183105469, + 62.04495620727539, + 61.957157135009766, + 61.91600036621094, + 61.91600036621094, + 61.91600036621094, + 61.95705795288086, + 62.06700134277344, + 62.1425895690918, + 62.12200164794922, + 62.053001403808594, + 61.97895812988281, + 61.93453598022461, + 61.94612503051758, + 62.06700134277344, + 62.160457611083984, + 62.189998626708984, + 62.17822265625, + 62.07951736450195, + 61.94300079345703, + 61.888999938964844, + 61.91362380981445, + 62.021018981933594, + 62.1231575012207, + 62.20317459106445, + 62.21699905395508, + 62.06700134277344, + 61.957000732421875 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 5.7488887731068665, + "values": [ + 68.0863265991211, + 67.91714477539062, + 68.68708038330078, + 68.88613891601562, + 68.95032501220703, + 68.96652221679688, + 69.00247192382812, + 69.15177917480469, + 69.3249740600586, + 69.57121276855469, + 69.7752914428711, + 69.91744232177734, + 69.94971466064453, + 69.90763092041016, + 69.8875503540039, + 69.89399719238281, + 69.92741394042969, + 69.98353576660156, + 70.00888061523438, + 70.02300262451172, + 69.98914337158203, + 69.99401092529297, + 70.04017639160156, + 70.09317016601562, + 70.16637420654297, + 70.05557250976562, + 68.67967224121094 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 6.388888758801752, + "values": [ + 69.58097839355469, + 70.0131607055664, + 70.08633422851562, + 70.01130676269531, + 69.95957946777344, + 69.90242004394531, + 69.91836547851562, + 69.96061706542969, + 69.99176788330078, + 70.01341247558594, + 70.05168914794922, + 70.05534362792969, + 70.01415252685547, + 69.79425811767578, + 69.11405181884766, + 68.70128631591797, + 69.1116714477539, + 69.0816650390625, + 68.903564453125, + 68.90020751953125, + 68.91948699951172, + 68.95985412597656, + 69.01729583740234, + 69.04063415527344, + 69.06002044677734, + 69.1363296508789, + 69.0469970703125, + 67.43706512451172, + 64.90077209472656 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 7.028888744496637, + "values": [ + 66.73137664794922, + 66.90548706054688, + 67.0513687133789, + 67.02515411376953, + 66.9941177368164, + 66.97207641601562, + 67.0168228149414, + 67.06500244140625, + 67.11521911621094, + 67.1485824584961, + 66.99476623535156, + 66.589599609375, + 65.79695892333984, + 64.9925537109375, + 64.86425018310547, + 64.79849243164062, + 64.77163696289062, + 64.79842376708984, + 64.85647583007812, + 64.89299774169922, + 64.92061614990234, + 65.00763702392578, + 65.18905639648438, + 65.35322570800781, + 65.41242218017578, + 65.15796661376953, + 63.29110336303711 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 7.668888730191522, + "values": [ + 65.86260986328125, + 66.64722442626953, + 66.98595428466797, + 66.98262786865234, + 66.92767333984375, + 66.96661376953125, + 67.08795166015625, + 67.24669647216797, + 67.32135009765625, + 67.10350036621094, + 65.67154693603516, + 62.290382385253906 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 7.988888723038965, + "values": [ + 66.4554672241211, + 67.87029266357422, + 68.71417236328125, + 69.375, + 69.74347686767578, + 69.9512939453125, + 70.07365417480469, + 70.1510009765625, + 70.09028625488281, + 69.62267303466797, + 67.16484832763672 + ] + }, + { + "hop_time": 0.019999999552965164, + "start_time": 8.308888715886408, + "values": [ + 68.33386993408203, + 68.7474594116211, + 68.98222351074219, + 69.0422134399414, + 69.06800079345703, + 69.05315399169922, + 68.9998550415039, + 68.95999908447266, + 68.9209976196289, + 68.9010009765625, + 68.89199829101562, + 68.91100311279297, + 68.97976684570312, + 69.0244140625, + 69.0479965209961, + 69.0479965209961, + 69.02899932861328, + 69.0080337524414, + 68.9809799194336, + 68.93482208251953, + 68.91100311279297, + 68.9010009765625, + 68.9010009765625, + 68.91100311279297, + 68.94246673583984, + 68.94999694824219, + 68.94999694824219, + 68.94000244140625, + 68.92342376708984, + 68.97077178955078, + 69.02348327636719, + 69.08204650878906, + 69.08030700683594, + 69.01737976074219, + 68.90448760986328, + 68.851318359375, + 68.86917114257812, + 68.98340606689453, + 69.13423919677734, + 69.24153137207031, + 69.2308120727539, + 69.07621765136719, + 68.86053466796875, + 68.63694763183594, + 68.59126281738281, + 68.75342559814453, + 69.04985046386719, + 69.32463836669922, + 69.39779663085938, + 69.24708557128906, + 68.93489837646484, + 68.58981323242188, + 68.4312744140625, + 68.54347229003906, + 68.84676361083984, + 69.1526870727539, + 69.35409545898438, + 69.32548522949219, + 69.12592315673828, + 68.86775207519531, + 68.65621185302734, + 68.63607025146484, + 68.80140686035156, + 68.96906280517578, + 69.18865966796875, + 69.38107299804688, + 69.37968444824219, + 69.04866790771484, + 68.67898559570312, + 68.62699890136719, + 68.70750427246094, + 68.8719711303711, + 69.19197845458984, + 69.30702209472656, + 69.29537200927734, + 69.01058959960938, + 68.77043151855469, + 68.68181610107422, + 68.7058334350586, + 68.86554718017578, + 69.18499755859375, + 69.33514404296875, + 69.3357925415039, + 69.14891815185547, + 68.77117919921875, + 68.60831451416016, + 68.67807006835938, + 68.89498901367188, + 69.11416625976562, + 69.34343719482422, + 69.36011505126953, + 69.23129272460938, + 69.05360412597656, + 68.83795928955078, + 68.67695617675781 + ] + } + ] + } + }, + "random_seed": 0 } \ No newline at end of file