From 453ca745af24b48ca9f8f1ab458749c4f29108c2 Mon Sep 17 00:00:00 2001 From: bgArray <474037765@qq.com> Date: Sat, 25 Jun 2022 23:20:56 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=AE=97=E6=B3=95=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 3 +- README_EN.md | 3 +- example_convert_mcpack.py | 7 + msctPkgver/load_InstrumentLabel.py | 51 ++++++ msctPkgver/main.py | 267 +++++++++++++++-------------- msctPkgver/program音色表.xlsx | Bin 0 -> 10787 bytes 6 files changed, 205 insertions(+), 126 deletions(-) create mode 100644 msctPkgver/load_InstrumentLabel.py create mode 100644 msctPkgver/program音色表.xlsx diff --git a/README.md b/README.md index b15dde9..a4f220d 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,8 @@ ## 致谢🙏 - 感谢 昀梦\ 找出指令生成错误bug并指正 -- 感谢由 Charlie_Ping “查理平” 带来的bdx文件转换参考 +- 感谢由 Charlie_Ping “查理平” 带来的bdx文件转换参考, +以及mid转我的世界乐器参考表格 - 感谢由 CMA_2401PT 为我们的软件开发进行指导 - 感谢由 Dislink Sforza \带来的midi音色解析以及转换指令的算法,我们将其加入了我们众多算法之一 - 感谢 Touch \提供的测试支持 diff --git a/README_EN.md b/README_EN.md index 054fb62..2c7ccfe 100644 --- a/README_EN.md +++ b/README_EN.md @@ -123,7 +123,8 @@ This is a default definder parameter: - Thank [Fuckcraft](https://github.com/fuckcraft) *(“鸣凤鸽子” ,etc)* for the function of Creating the Websocket Server for Minecraft: Bedrock Edition. - *!! They have given me the rights to directly copy the lib into Musicreater* - Thank *昀梦*\ for finding and correcting the bugs in the commands that *Musicreater* Created. -- Thank *Charlie_Ping “查理平”* for bdx convert funtion. +- Thank *Charlie_Ping “查理平”* for bdx convert function, and +the data label that's used to convert the mid's instruments into minecraft's instruments. - Thank *CMA_2401PT* for BDXWorkShop as the .bdx structure's operation guide. - Thank *Miracle Plume “神羽”* \ for the Miracle Plume Bedrock Edition Audio Resource Pack - 感谢由 Dislink Sforza \带来的midi转换算法,我们将其加入了我们众多算法之一 diff --git a/example_convert_mcpack.py b/example_convert_mcpack.py index ece9943..7f3202f 100644 --- a/example_convert_mcpack.py +++ b/example_convert_mcpack.py @@ -13,3 +13,10 @@ convertion.tomcpack( float(input('请输入音量(0-1):')), float(input('请输入速度倍率:')), ) + +# for the test +# if __name__ == '__main__': +# convertion = midiConvert() +# convertion.convert(r"C:\Users\lc\Documents\MuseScore3\乐谱\乐谱\victory.mid", ".") +# convertion.tomcpack( +# 1, True, True, "scb", 1, 1) diff --git a/msctPkgver/load_InstrumentLabel.py b/msctPkgver/load_InstrumentLabel.py new file mode 100644 index 0000000..884a2f9 --- /dev/null +++ b/msctPkgver/load_InstrumentLabel.py @@ -0,0 +1,51 @@ +from openpyxl import * + + +def get(): + wb = load_workbook('program音色表.xlsx') + + ws = wb.active + # 所有行 + keys = [] + values = [] + for row in ws.iter_rows(): + for cell in row: + # print(cell.value) + try: + keys.append(int(cell.value)) + except ValueError: + values.append(cell.value) + # # 所有列 + # for column in ws.iter_cols(): + # for cell in column: + # print(cell.value) + out = "" + index = 0 + for i in keys: + out += ", \"" + str(i) + "\": \"" + values[index] + "\"" + index += 1 + + print(out) + + +idList = {"0": "harp", "1": "harp", "2": "pling", "3": "harp", "4": "pling", "5": "pling", "6": "harp", "7": "harp", + "8": "share", "9": "harp", "10": "didgeridoo", "11": "harp", "12": "xylophone", "13": "chime", "14": "harp", + "15": "harp", "16": "bass", "17": "harp", "18": "harp", "19": "harp", "20": "harp", "21": "harp", + "22": "harp", + "23": "guitar", "24": "guitar", "25": "guitar", "26": "guitar", "27": "guitar", "28": "guitar", + "29": "guitar", + "30": "guitar", "31": "bass", "32": "bass", "33": "bass", "34": "bass", "35": "bass", "36": "bass", + "37": "bass", + "38": "bass", "39": "bass", "40": "harp", "41": "harp", "42": "harp", "43": "harp", "44": "iron_xylophone", + "45": "guitar", "46": "harp", "47": "harp", "48": "guitar", "49": "guitar", "50": "bit", "51": "bit", + "52": "harp", "53": "harp", "54": "bit", "55": "flute", "56": "flute", "57": "flute", "58": "flute", + "59": "flute", "60": "flute", "61": "flute", "62": "flute", "63": "flute", "64": "bit", "65": "bit", + "66": "bit", "67": "bit", "68": "flute", "69": "harp", "70": "harp", "71": "flute", "72": "flute", + "73": "flute", "74": "harp", "75": "flute", "76": "harp", "77": "harp", "78": "harp", "79": "harp", + "80": "bit", "81": "bit", "82": "bit", "83": "bit", "84": "bit", "85": "bit", "86": "bit", "87": "bit", + "88": "bit", "89": "bit", "90": "bit", "91": "bit", "92": "bit", "93": "bit", "94": "bit", "95": "bit", + "96": "bit", "97": "bit", "98": "bit", "99": "bit", "100": "bit", "101": "bit", "102": "bit", "103": "bit", + "104": "harp", "105": "banjo", "106": "harp", "107": "harp", "108": "harp", "109": "harp", "110": "harp", + "111": "guitar", "112": "harp", "113": "bell", "114": "harp", "115": "cow_bell", "116": "basedrum", + "117": "bass", "118": "bit", "119": "basedrum", "120": "guitar", "121": "harp", "122": "harp", "123": "harp", + "124": "harp", "125": "hat", "126": "basedrum", "127": "snare"} diff --git a/msctPkgver/main.py b/msctPkgver/main.py index c0758ec..4ed7ae6 100644 --- a/msctPkgver/main.py +++ b/msctPkgver/main.py @@ -88,49 +88,66 @@ class midiConvert: :param default: 如果instrumentID不在范围内,返回的默认我的世界乐器名称 :return: (str我的世界乐器名, int转换算法中的X)""" - if instrumentID == 105: - return 'note.banjo', 6 - if instrumentID in range(32, 40): - return 'note.bass', 8 - if instrumentID in range(115, 119): - return 'note.basedrum', 7 # 注意,这里是底鼓,打击乐器无音域 - if instrumentID == 9 or instrumentID == 14: - return 'note.bell', 4 - if instrumentID == 80 or instrumentID == 81: - return 'note.bit', 6 - if instrumentID == 112: - return 'note.cow_bell', 5 - if instrumentID == -1: - return 'note.didgeridoo', 8 - if instrumentID in range(72, 80): - return 'note.flute', 5 - if instrumentID in range(24, 32): - return 'note.guitar', 7 - if instrumentID == -2: - return 'note.hat', 7 # 注意,这里是击鼓沿,打击乐器无音域 - if instrumentID == 14: - return 'note.chime', 4 - if instrumentID == 8 or instrumentID == 11: - return 'iron_xylophone', 6 - if instrumentID == 2: - return 'note.pling', 6 - if instrumentID == 114: - return 'note.snare', 7 # 注意,这里是小军鼓,打击乐器无音域 - if instrumentID == 13: - return 'note.xylophone', 4 - return 'note.harp', 6 + import msctPkgver.load_InstrumentLabel + X_Range = {"harp": 6, "pling": 6, "banjo": 6, "iron_xylophone": 6, "guitar": 7, "bass": 8, + "didgeridoo": 8, "flute": 5, "xylophone": 4, "cou_bell": 5, "chime": 4, "bell": 4, + "bit": 6, "basedrum": 7, "hat": 7, "snare": 7} + + # 以下为旧版运算算法 + + # if instrumentID == 105: + # return 'note.banjo', 6 + # if instrumentID in range(32, 40): + # return 'note.bass', 8 + # if instrumentID in range(115, 119): + # return 'note.basedrum', 7 # 注意,这里是底鼓,打击乐器无音域 + # if instrumentID == 9 or instrumentID == 14: + # return 'note.bell', 4 + # if instrumentID == 80 or instrumentID == 81: + # return 'note.bit', 6 + # if instrumentID == 112: + # return 'note.cow_bell', 5 + # if instrumentID == -1: + # return 'note.didgeridoo', 8 + # if instrumentID in range(72, 80): + # return 'note.flute', 5 + # if instrumentID in range(24, 32): + # return 'note.guitar', 7 + # if instrumentID == -2: + # return 'note.hat', 7 # 注意,这里是击鼓沿,打击乐器无音域 + # if instrumentID == 14: + # return 'note.chime', 4 + # if instrumentID == 8 or instrumentID == 11: + # return 'iron_xylophone', 6 + # if instrumentID == 2: + # return 'note.pling', 6 + # if instrumentID == 114: + # return 'note.snare', 7 # 注意,这里是小军鼓,打击乐器无音域 + # if instrumentID == 13: + # return 'note.xylophone', 4 + # return 'note.harp', 6 + + # 以下为新版运算算法 + + # print(instrumentID) + # print("note." + msctPkgver.load_InstrumentLabel.idList.get(str(instrumentID))) + # print(X_Range.get(msctPkgver.load_InstrumentLabel.idList.get(str(instrumentID)))) + + return "note." + msctPkgver.load_InstrumentLabel.idList.get(str(instrumentID)), \ + X_Range.get(msctPkgver.load_InstrumentLabel.idList.get(str(instrumentID))) + def __score2time(self, score: int): return str(int(int(score / 20) / 60)) + ':' + str(int(int(score / 20) % 60)) def __formProgressBar( - self, - maxscore: int, - scoreboardname: str, - progressbar: tuple = ( - r'▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]', - ('§e=§r', '§7=§r'), - ), + self, + maxscore: int, + scoreboardname: str, + progressbar: tuple = ( + r'▶ %%N [ %%s/%^s %%% __________ %%t|%^t ]', + ('§e=§r', '§7=§r'), + ), ) -> list: pgsstyle = progressbar[0] @@ -149,7 +166,7 @@ class midiConvert: ''' def __replace( - s: str, tobeReplaced: str, replaceWith: str, times: int, other: str + s: str, tobeReplaced: str, replaceWith: str, times: int, other: str ): if times == 0: return s.replace(tobeReplaced, other) @@ -224,16 +241,16 @@ class midiConvert: return finalprgsbar def __formCMDblk( - self, - command: str, - particularValue: int, - impluse: int = 0, - condition: bool = False, - needRedstone: bool = True, - tickDelay: int = 0, - customName: str = '', - executeOnFirstTick: bool = False, - trackOutput: bool = True, + self, + command: str, + particularValue: int, + impluse: int = 0, + condition: bool = False, + needRedstone: bool = True, + tickDelay: int = 0, + customName: str = '', + executeOnFirstTick: bool = False, + trackOutput: bool = True, ): """ 使用指定项目返回指定的指令方块放置指令项 @@ -296,7 +313,7 @@ class midiConvert: return block def _toCmdList_m1( - self, scoreboardname: str = 'mscplay', volume: float = 1.0, speed: float = 1.0 + self, scoreboardname: str = 'mscplay', volume: float = 1.0, speed: float = 1.0 ) -> list: """ 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表 @@ -322,12 +339,14 @@ class midiConvert: for msg in track: ticks += msg.time + # print(msg) if msg.is_meta: if msg.type == 'set_tempo': tempo = msg.tempo - if msg.type == 'program_change': - instrumentID = msg.program else: + if msg.type == 'program_change': + # print("TT") + instrumentID = msg.program if msg.type == 'note_on' and msg.velocity != 0: nowscore = round( (ticks * tempo) @@ -341,7 +360,7 @@ class midiConvert: + '=' + str(nowscore) + '}' - + f'] ~ ~ ~ playsound {soundID} @s ~ ~{1/volume-1} ~ {msg.velocity*(0.7 if msg.channel == 0 else 0.9)} {2**((msg.note-60-_X)/12)}' + + f'] ~ ~ ~ playsound {soundID} @s ~ ~{1 / volume - 1} ~ {msg.velocity * (0.7 if msg.channel == 0 else 0.9)} {2 ** ((msg.note - 60 - _X) / 12)}' ) commands += 1 if len(singleTrack) != 0: @@ -350,11 +369,11 @@ class midiConvert: return tracks, commands, maxscore def _toCmdList_withDelay_m1( - self, - volume: float = 1.0, - speed: float = 1.0, - player: str = '@a', - isMixedWithPrograssBar=False, + self, + volume: float = 1.0, + speed: float = 1.0, + player: str = '@a', + isMixedWithPrograssBar=False, ) -> list: """ 使用Dislink Sforza的转换思路,将midi转换为我的世界命令列表,并输出每个音符之后的延迟 @@ -398,11 +417,11 @@ class midiConvert: soundID, _X = self.__Inst2soundIDwithX(instrumentID) try: tracks[nowtick].append( - f'execute {player} ~ ~ ~ playsound {soundID} @s ~ ~{1/volume-1} ~ {msg.velocity*(0.7 if msg.channel == 0 else 0.9)} {2**((msg.note-60-_X)/12)}' + f'execute {player} ~ ~ ~ playsound {soundID} @s ~ ~{1 / volume - 1} ~ {msg.velocity * (0.7 if msg.channel == 0 else 0.9)} {2 ** ((msg.note - 60 - _X) / 12)}' ) except: tracks[nowtick] = [ - f'execute {player} ~ ~ ~ playsound {soundID} @s ~ ~{1/volume-1} ~ {msg.velocity*(0.7 if msg.channel == 0 else 0.9)} {2**((msg.note-60-_X)/12)}', + f'execute {player} ~ ~ ~ playsound {soundID} @s ~ ~{1 / volume - 1} ~ {msg.velocity * (0.7 if msg.channel == 0 else 0.9)} {2 ** ((msg.note - 60 - _X) / 12)}', ] allticks = list(tracks.keys()) @@ -425,7 +444,7 @@ class midiConvert: ''' def __replace( - s: str, tobeReplaced: str, replaceWith: str, times: int, other: str + s: str, tobeReplaced: str, replaceWith: str, times: int, other: str ): if times == 0: return s.replace(tobeReplaced, other) @@ -522,13 +541,13 @@ class midiConvert: return math.ceil(math.sqrt(math.ceil(total / maxHeight))) def tomcpack( - self, - method: int = 1, - isAutoReset: bool = False, - progressbar=None, - scoreboardname: str = 'mscplay', - volume: float = 1.0, - speed: float = 1.0, + self, + method: int = 1, + isAutoReset: bool = False, + progressbar=None, + scoreboardname: str = 'mscplay', + volume: float = 1.0, + speed: float = 1.0, ) -> bool: """ 使用method指定的转换算法,将midi转换为我的世界mcpack格式的包 @@ -558,7 +577,7 @@ class midiConvert: # 写入manifest.json if not os.path.exists(f'{self.outputPath}/temp/manifest.json'): with open( - f"{self.outputPath}/temp/manifest.json", "w", encoding='utf-8' + f"{self.outputPath}/temp/manifest.json", "w", encoding='utf-8' ) as f: f.write( "{\n \"format_version\": 1,\n \"header\": {\n \"description\": \"" @@ -575,7 +594,7 @@ class midiConvert: ) else: with open( - f'{self.outputPath}/temp/manifest.json', 'r', encoding='utf-8' + f'{self.outputPath}/temp/manifest.json', 'r', encoding='utf-8' ) as manifest: data = json.loads(manifest.read()) data['header'][ @@ -599,9 +618,9 @@ class midiConvert: 'function mscplay/track' + str(cmdlist.index(track) + 1) + '\n' ) with open( - f'{self.outputPath}/temp/functions/mscplay/track{cmdlist.index(track)+1}.mcfunction', - 'w', - encoding='utf-8', + f'{self.outputPath}/temp/functions/mscplay/track{cmdlist.index(track) + 1}.mcfunction', + 'w', + encoding='utf-8', ) as f: f.write('\n'.join(track)) indexfile.writelines( @@ -612,12 +631,12 @@ class midiConvert: + scoreboardname + ' 1\n', ( - 'scoreboard players reset @a[scores={' - + scoreboardname - + '=' - + str(maxscore + 20) - + '..}]' - + f' {scoreboardname}\n' + 'scoreboard players reset @a[scores={' + + scoreboardname + + '=' + + str(maxscore + 20) + + '..}]' + + f' {scoreboardname}\n' ) if isAutoReset else '', @@ -628,18 +647,18 @@ class midiConvert: if progressbar: if progressbar == True: with open( - f'{self.outputPath}/temp/functions/mscplay/progressShow.mcfunction', - 'w', - encoding='utf-8', + f'{self.outputPath}/temp/functions/mscplay/progressShow.mcfunction', + 'w', + encoding='utf-8', ) as f: f.writelines( '\n'.join(self.__formProgressBar(maxscore, scoreboardname)) ) else: with open( - f'{self.outputPath}/temp/functions/mscplay/progressShow.mcfunction', - 'w', - encoding='utf-8', + f'{self.outputPath}/temp/functions/mscplay/progressShow.mcfunction', + 'w', + encoding='utf-8', ) as f: f.writelines( '\n'.join( @@ -658,15 +677,15 @@ class midiConvert: shutil.rmtree(f'{self.outputPath}/temp/') def toBDXfile( - self, - method: int = 1, - author: str = 'Eilles', - progressbar=False, - maxheight: int = 64, - scoreboardname: str = 'mscplay', - volume: float = 1.0, - speed: float = 1.0, - isAutoReset: bool = False, + self, + method: int = 1, + author: str = 'Eilles', + progressbar=False, + maxheight: int = 64, + scoreboardname: str = 'mscplay', + volume: float = 1.0, + speed: float = 1.0, + isAutoReset: bool = False, ): """ 使用method指定的转换算法,将midi转换为BDX结构文件 @@ -695,9 +714,9 @@ class midiConvert: f.write("BD@") _bytes = ( - b"BDX\x00" - + author.encode("utf-8") - + b" & Musicreater\x00\x01command_block\x00" + b"BDX\x00" + + author.encode("utf-8") + + b" & Musicreater\x00\x01command_block\x00" ) key = { @@ -726,12 +745,12 @@ class midiConvert: if isAutoReset: commands += ( - 'scoreboard players reset @a[scores={' - + scoreboardname - + '=' - + str(maxScore + 20) - + '}] ' - + scoreboardname + 'scoreboard players reset @a[scores={' + + scoreboardname + + '=' + + str(maxScore + 20) + + '}] ' + + scoreboardname ) if progressbar: @@ -747,13 +766,13 @@ class midiConvert: cmd, (1 if yforward else 0) if ( - ((nowy != 0) and (not yforward)) - or ((yforward) and (nowy != maxheight)) + ((nowy != 0) and (not yforward)) + or ((yforward) and (nowy != maxheight)) ) else (3 if zforward else 2) if ( - ((nowz != 0) and (not zforward)) - or ((zforward) and (nowz != _sideLength)) + ((nowz != 0) and (not zforward)) + or ((zforward) and (nowz != _sideLength)) ) else 5, impluse=2, @@ -775,7 +794,7 @@ class midiConvert: nowz += 1 if zforward else -1 if ((nowz > _sideLength) and (zforward)) or ( - (nowz < 0) and (not zforward) + (nowz < 0) and (not zforward) ): nowz -= 1 if zforward else -1 zforward = not zforward @@ -795,14 +814,14 @@ class midiConvert: return (True, _bytes, (nowx, maxheight, _sideLength)) def toBDXfile_withDelay( - self, - method: int = 1, - author: str = 'Eilles', - progressbar=False, - maxheight: int = 64, - volume: float = 1.0, - speed: float = 1.0, - player: str = '@a', + self, + method: int = 1, + author: str = 'Eilles', + progressbar=False, + maxheight: int = 64, + volume: float = 1.0, + speed: float = 1.0, + player: str = '@a', ): """ 使用method指定的转换算法,将midi转换为BDX结构文件 @@ -828,9 +847,9 @@ class midiConvert: f.write("BD@") _bytes = ( - b"BDX\x00" - + author.encode("utf-8") - + b" & Musicreater\x00\x01command_block\x00" + b"BDX\x00" + + author.encode("utf-8") + + b" & Musicreater\x00\x01command_block\x00" ) key = { @@ -857,13 +876,13 @@ class midiConvert: cmd, (1 if yforward else 0) if ( - ((nowy != 0) and (not yforward)) - or ((yforward) and (nowy != maxheight)) + ((nowy != 0) and (not yforward)) + or ((yforward) and (nowy != maxheight)) ) else (3 if zforward else 2) if ( - ((nowz != 0) and (not zforward)) - or ((zforward) and (nowz != _sideLength)) + ((nowz != 0) and (not zforward)) + or ((zforward) and (nowz != _sideLength)) ) else 5, impluse=2, @@ -885,7 +904,7 @@ class midiConvert: nowz += 1 if zforward else -1 if ((nowz > _sideLength) and (zforward)) or ( - (nowz < 0) and (not zforward) + (nowz < 0) and (not zforward) ): nowz -= 1 if zforward else -1 zforward = not zforward diff --git a/msctPkgver/program音色表.xlsx b/msctPkgver/program音色表.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..b68bcf663ed6d7f034064830c05c7311e461cb57 GIT binary patch literal 10787 zcmeHtby!qe7xy6D!!Sw+4qeiWfPm6ScL;)XNXsB0(lvlcgQN&Z4hRcGz>+o}ra7+?Sv00#g7&;rB-{08jN000b3 z0Du&LbK{AmgT1r4y|baFyQ4YGfYZ&+mL>z^21`2N2I~3$ZU4m*DAeq-Z{`Np>#U1P zHE4&t56;iWp{k{N!mx_{)-C!mL*+6B%Y`Sig_>3=P7|Y=?Z6YJ0}*n>6qBxm*rF}x znXU?h5q?fM!fEfprR+OtzpO&-@UqCTd+FqaJ3Ck-71)Z^Qzo3&bXRnYx9*kW@vSuN zE3Ziuz(pk)>)F2L-#DGvc+l0XdqaWs1-Xf!o4+~-rTkZ&PoLCy`Lr2|lVQ|aoUte2 ztl(1Bn({M<+gRY*uf^W^8+G;qg&5>Q~3nYtOGLNfmNC`p2$7-`T!hQ5b)Uy;A`y`$srXCabp#C?@(KSY?tgjmgvnFLBWWW zDL{g&FU7-V1|K_F@THN%9 zDBDv+-Ni?_siBj(EsTrvdjCK7{4e&&zYM)FT1lman;>XM_9D3RWOODHD52mgE?Y;d z>E$CgO#)YnEPSWKXITIKZhxiA(x{D(KdOL|?sj zCEc$k-;2;jB{a4WK_yiJ>FY5A$=>7f*=vNN@cW9J!-+RLVJ4Q7MXx$-X^&1RwKS~m zKPxdwa}uI+H#D>Td?1zFNP78-Lph~Woem26&O7j^gE9TcTdy3t*OThrDuCNk(7D^? z7Y51wiuz*x(@A_E#wHw~BvOlV5=sCLnwu@xfBeMN!O6zN!NKPGYxalFprJl#C@%lq zTcNs=LMtkyVgG0y^nY)7{)q5XMVV+Z|y{hrWY8bR3f{#2vp!n53Z1^^Jlpe4->6Y0bmvPWCjg z;J5EAj1<9{*>qBeHN!zr>wfMRywTZ3K|6705cXOB7~n@AoC5XSP))6Z`0ccCKH6J9;I@`%T9-Si4DxlOPmc#< zwkXaMPmU%E&I`1cC{hx}z|h3jSRazg*l(JHkYJUe6cD7EwdNxh-b-|qo$;Mn1m{?4 z#8?{cjn`aO)DmT8yn%@2@O~{dZo}eu0E+!fRpo>w}E+b9anka`Yr`9Pq03)=Ca{TQ8Gte0wb7s(hh|q!r7NSotCBc?(Butm>Y2AbO=aJA6+hEGA1BS+T3u+;-LG ztl;Dson4;bx8Pr0|JLwqH-=r(U=XSV>m@E7piy}t=8|MtM%4GT0UG^}OB# zH@75WOIR|t`a^37GNE3O{|>1>uJbtIWxI$0!$=X9ehV4oEUH}j)}du|8)SPy`c_-r z8hiJ*FldtH2=wf&-N?+QebtPeMdJ?#u03h?j-A20!Ww++jy4L>A%R@}X;TMm$y_on z3(dZEjg5b|1{SR$Dg~4^gcAS&;Qv?y%*xow+)TsS>6yJH>^gpI>L{t8(*o<0zeD(^ zIVyOp2^5Hx6hFBS!df_;$oV^f(>Np?9Q4a&U9M*5!{Y&doAc-QS*`#zhR;NWX+K-j&l^E=>|^ z@aF8}M0+ra2PUL;s=yy+YAS|ESTi=3<}|IS`t#(VjUk(EI%q6BE)@+pBo!x;6FplM z#7GrS;fvSjA&g<{BB#V0Mz z`phKQv17<=^zD$Wg>p~`-Jx(O=t*NLzVE%89|kxQG9Oy@$Fuee>zCGYH@`EK(rqL? z!~Zu|6t?44XhI2H0;K@@pRNeAGB#sUc> zH3aCYwN}tg&Ze-C36Uq@OOG-t^Uur6jswg5$>$<@w=IaG*uT`?2wXBHvntv3nBr&@ zFBM#$2IF#wL>iB9HYA}dE6ay;L_WTWUmB>!a7o_c)jC=F1~)JA;kQY-5O?mb)3Dov zh56QY`-a%%T*?(Y>`R_H{z4Dbb$@KW@q~!jXEyfB?hgdW-H$YSm?3+=Q+jLP$3%R? z^*GJOlL#Z*uD9pl828^K(38&;oP`Ad#86((#eNG$&Q|7j=3Kw+e;f9u?vt?F0$?w$ zLs5c*?eF}HJs^jY4wqpI0bE^X#)yv`9ObXvJOSg35!m6X8~+>TIK-By^=4tt>an5|~MOoBJUuUYDzf+N^xFnc(k!Z3N4rbk4?_T?+*s~Z{FQm3No>$ev!w2$+t@<@!)rM?~7gi;L3}V)ij4F6H_z zS_ro*=e*hr`v-1C5X-wagOeZqIyk3CEkV@r#+$dHLr~PnG;P zp1$qHEG>V<^O|6e)3m$=RDjOeT?r*7{dG+M$}hV;`rEu4Jl@`YbvhYZR%IYhm7){nr4AA&W$ z9k#!PJxT4sJ=DQJlU617m~dUAFL+hhOGP3Q@82PF_A61R81#}HrU$*t z-RX#(xpIx2DH_U2Uvp`>lIla^mcGfu?|{A{-4@NAHh@MH88Xk%O!QiYhgHrc`5>`H#24_IQxiC)^P(lY%qF@jXrsNuk(} z=(xB}3!Elw;9!G-6_1iQ_lknl{QCW9pTct?hVphrkQRQ(*+*c;E-xtGGN(D?nekBM zw}kh4GS8Rs(knyp*i)z;+gIq%Dh!|*DFm|I=@IC@ZBIKJE^@1t?8*59QQ3gjz~k{w37V(R8qs9vSJlTD

Ybuvqxs5$=&yPY#DN2nE5 zmTjOo`<0=%w3}JYUyE(V8u=N9u;HvA0*wkOq;PLEGzX-s_1xymm22PNWxpqU_g-G= zf}hxdl%4;qHY{M2)IsP#vVKwbE;D?&R-Lh-_G)m4X=0ZN{)kN3cSY0jrhK)#ERg+d z7`CLzZv>wPO;VDirMOGzZEr=-9{Ytt!19ERI=lH#^9Fa3qY}uIg5@+r&KJUQ!rY~B zZnpEa83hPY#wuN%uiCOr75C77;Z`qPS~}ZaH#D@mHjq`vwu&3wy>USZLu$nZG3=n7 zoT~R;*X{)WY1Ie|)L$=W5P^AQ-+23bxW(0*4JwwB@gl6Jh;f!L-Qudd){JtEL+oGb z^hbOoI@Ezo$J%(L_+Wwl>&Jy;+fjf#j3J$ z$kYwGzT6R4a$Jm)8uf6N6MXz?lSwVRdbrbPBGSe#N@}ou@!Q5qvcoJwM;v1j*-rUb z(jaY2i5u8MX`rxZ|2E`-I{{2pLzl!roF2M~)#4qtIQ}Wg`Ot}E5lT%p*@)bW7}ev; zAF_G*LZCEBDp~R}kA|%MRC(gFHBJzNUmA~Q512pK;GKhls;Y#Cfch7&2sf^KlV{=+ zb#r5AF;?%Li3alS<{_S%%F`+1qeoj;>Q5G{F6QMVzGW9m{bR?zMYx>p zfta=H?Wm7SPmpJJ8sqxD1}c6}ZfvTHS8ADFES{aMB&h60>IqquT1<1FitYO(IvhK_ ztW0X}f1|f*09L)1gQ*LHH3+}n^STWEX3IfSHMT)mrswiC#s9PzQBQjep)b5st!m^gh!7+Y znqfjliz1OG(@5yyh8Omf=XQ+I5_Mq1N*4NnYY;uJOM9mHYSw#5@1vGUxwkM@$20x8gV zv07kB@a5&Gz_?+)>6PgVp`qeT=lTpDck3jTLnJXg)M#XFHd4ubb)%}g)uuq}q^h#v z)Lv%s(*N?@1thu_b%8K=pwqE`katyox)0N8{B$gse8~AV1C|@H{jRa0xE37?`-kh^ z+kx|sTqneK2j+p#+t*yjqbz{x*p9TS@< zMjI=~#NAci6^}YK`$+nBvs|uSF&((hxv((Inqm_}&F9-}Wzu28zuPt~Ra~(((_c1b zrP-UrT5jA=AlH|^Sm_i?n0U&jj}_0Q+T4q5n7a$6e18#(gAs($E#4H1>7v|E8vR*} z5IQF)<{?Or3yh`pTaXp2yY~^_C@l&DuUl38IeYpH<)k#XK(voI1ux4Rta5Cf*yzt_ zP0%%P2YN_Ad{H#uM$;6W-cL$~zOH=5fY~>IH&&I3)T&CR;iy||uTf`V&(C_5Tlbuj zatAEc{(93o%}|5U2u?^2W=5a#-(ZQ4L~)bhVMY(ZaK+8A^P`pAv;}9Im00rZ2;ovJ z7#Dx;@;P>t0r9p55ul!$LJVQl9K{c#^(!?Q z%pftEw<#bZ98jrpT41s)j<3;HE3P=*SQku5Maq>FizEal$|D`H(i|%b^fgKaJ;Df? zh!v>|mh!{Fuwj0K1HyGp!ypL}mue>QJr0Q}K4E~%63~I(;FLef`6AH8_(zM2oEeu6 zr%S$BStyZ|81m~sk`!6fw+ zu`NS@EFrDBGO8cU0~M@sD1`|zqJ~MKv9E-U3sG+gjy*uzim6yyt3w13oOe+ez`FSt%-9%H%&g%K_?A^w!q@V}4mt3Zl0c5@`(aoOHF1L)J!C6LJV5JV#3TOtErMHA-Gnx;Y)eWjZ3%k4(q z7FnVqwlbV!n3uHw9tnH+o1}&lz^O>Q-}+5eDL)Z9z9KhH~0S_m|jYC>rNQyC-!4kE#{#F!#` z{g=!4<;c~@Wxy#UR__GeD~kgINUmSW=O@+7C(#UV=FV`;|6t)tafdQb_6Ba~m~Ksl zI-lZqw&!%X%;1bJf^22;u}$BADlEljyqkp58c`be0_lw>@@1RxL-}j&f6&MxO`)mP z(^j41k5!RQq13bVO%AxtMroZ(mjlOp#Pnc|S^!sA6AD(BR%X2UMsZq1;E*kP*FU4L z`u?(J7HMsndK3mN*seDvnr#@@Zd!}sJ&yEkzJ3w}DKth)pKj{yeHz--A=`ak*wX~2 z!a6FkebKlCjYv29;G1zuoQ5{0dmmb&bWcANGsvlJWqr1rgFo@RU(j?o^lqIQ(*j*) znzvV%+G5?EB{LVUKF4p=ifGfzqf0{ z>KjvYCxUwf(VG%yp{#CcPxmGrPlL;@N(83;?mp{;X<&Z_VHZMbByUKwj84fxCA1ql zs+xmq*Fc}BWvzURzDG$a+HE3Aa5M?;QS&$@TZMkZRFI#NtZ*KOTVj6hw7L<$Pix-P z7aolC zlL_eM);sYN(^2n+qN3#rYtgZ*W~xY=Dav=AYbtwp#>ca_kRP|_T@4r6k;DU`%GP{d zg4I{3lK(&RFk_5M4oOrIQVlh0Awj`NW)7yRP7aPRE>j05^Xu9p3UU1(dW1?)Uq!1a zv~Yt1XECoNyF8N3W@HHMu-QDEXdbJ*!ta{VwjGX?s=D+TaTy~Dia1G&*-7(|YY@t3 zI>Xvn*CvBAVMwP--?gc~J7`n!h7Wvypkhg>;Jp~Lqm|azuS?C`LwatN2(%3wM>U#f z9RaL^Up-p5T}+5W>lg%`fmH~t>EPk*lKF+09l!vNt_fzE!uI`bpHhmD)z`5mQb}CM zLjWr`G+oRi_?g7syK#T5?REdBiGo^NDomTsZ-mVic;K1mtLsBhCxQyn;`tX{Js z^@j!R72EcVfVdj4;^Vo73iCHP(t`Resj~sM9c&^`a?%agXU~;WT+>Z(@{A!2OZCYzB?8 zsc@k4bLZzS?l5EkJ!a>ECEUp5#xRLuuh?)@Q-Fj=WCfqZeVQa(xcH$rjpGS{Oew0~ z{4YKrWN=85gz^C(YEBRQs}C4EI{wcEP$K(%ONnl>M_z;Om}f*m+uN3zLVLM+KUOvG0BSyyid_1KZ)TdO4fQW*Zvv4 zxk+?Jn=U@3XK(bWvW>9$Btm|09$ct3#1b4ZT0tlWK}K?SdwY95xVYTAT5)KSgIIeC z+EaIY%S<^Y1XJ<5V4-8<1_#{rzcpNC{hqD?O@tc#gaCcpr>qdo(GAu+uA<0QzngZ> z#8O3X4DsaI=0vn~8?puD*xk9+_EDQeUg)zI^4zaFTUrL0*lV-Quln%l)2&+MECEDc zd4TCOIHg}GANR*a-10Sa^q-iHc7p?j;{E$Vw?Fjv$MIj5y{Rhvox$Ii;QT>&eat~Q z$zK-a{7U%uWeI;J>_s`>|9z3duY7*Rs(*62LapWd1-br}_*ZE2Cov%kjYARt1L*vf z!>{<Um5-icl~7e8dWGnG5i($`jx@2rNy5Nj8W+A^_c!FH2zBZ zYpvxcX&BkBgZWu``IXDxGlic#P}NRa0N@{a!>{DOhWnok!ckU?>i_>w@c)(R-y`== gmH+@5!(aOJdmL9)z`Xtd00>Y&CX^YkFkQF)524n8RsaA1 literal 0 HcmV?d00001