diff --git a/Musicreater/__init__.py b/Musicreater/__init__.py index 9567565..6105ec3 100644 --- a/Musicreater/__init__.py +++ b/Musicreater/__init__.py @@ -17,8 +17,8 @@ Terms & Conditions: License.md in the root directory # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md -__version__ = "1.6.1" -__vername__ = "更新乐器对照表" +__version__ = "1.6.2" +__vername__ = "新的转换算法测试,更好的声音适配" __author__ = ( ("金羿", "Eilles Wan"), ("诸葛亮与八卦阵", "bgArray"), diff --git a/Musicreater/experiment.py b/Musicreater/experiment.py index 26771fa..8d1da2b 100644 --- a/Musicreater/experiment.py +++ b/Musicreater/experiment.py @@ -25,6 +25,7 @@ from .subclass import * from .utils import * from .types import Tuple, List, Dict + class FutureMidiConvertRSNB(MidiConvert): """ 加入红石音乐适配 @@ -57,101 +58,6 @@ class FutureMidiConvertRSNB(MidiConvert): except KeyError: return "air" - def to_note_list_in_delay( - self, - ) -> Tuple[Dict[int, SingleNoteBox], int]: - """ - 使用金羿的转换思路,将midi转换为我的世界音符盒音高、乐器及延迟列表,并输出每个音符之后的延迟 - - Returns - ------- - tuple( Dict[int, SingleNoteBox], int音乐时长游戏刻 ) - """ - - self.to_music_channels() - - tracks = {} - note_range = {} - InstID = -1 - # cmd_amount = 0 - - # 此处 我们把通道视为音轨 - for i in self.channels.keys(): - # 如果当前通道为空 则跳过 - if not self.channels[i]: - continue - - # 第十通道是打击乐通道 - SpecialBits = True if i == 9 else False - - for track_no, track in self.channels[i].items(): - for msg in track: - if msg[0] == "PgmC": - InstID = msg[1] - - elif msg[0] == "NoteS": - # block_id = self.soundID_to_block(( - # self.perc_inst_to_soundID_withX(msg[1]) - # if SpecialBits - # else self.inst_to_souldID_withX(InstID) - # )[0]) - - # delaytime_now = round(msg[-1] / 50) - - note_ = 2 ** ((msg[1] - 60) / 12) - - try: - tracks[msg[-1]].append( - ( - InstID, - note_, - ) - ) - except KeyError: - tracks[msg[-1]] = [(InstID, note_)] - - try: - note_range[InstID]["max"] = max( - note_range[InstID]["max"], note_ - ) - note_range[InstID]["min"] = min( - note_range[InstID]["min"], note_ - ) - except KeyError: - note_range[InstID] = {"max": note_, "min": note_} - - del InstID - all_ticks = list(tracks.keys()) - all_ticks.sort() - results = [] - print(note_range) - - exit() - - for i in range(len(all_ticks)): - for j in range(len(tracks[all_ticks[i]])): - results.append( - SingleCommand( - tracks[all_ticks[i]][j], - tick_delay=( - 0 - if j != 0 - else ( - all_ticks[i] - all_ticks[i - 1] - if i != 0 - else all_ticks[i] - ) - ), - annotation="在{}播放{}%的{}音".format( - mctick2timestr(i), max_volume * 100, "" - ), - ) - ) - - self.music_command_list = results - self.music_tick_num = max(all_ticks) - return results, self.music_tick_num - class FutureMidiConvertM4(MidiConvert): """ @@ -163,172 +69,50 @@ class FutureMidiConvertM4(MidiConvert): @staticmethod def _linear_note( _note: SingleNote, - _apply_time_division: int = 100, - ) -> List[Tuple[int, int, int, int, float],]: + _apply_time_division: float = 100, + ) -> List[SingleNote]: """传入音符数据,返回以半秒为分割的插值列表 :param _note: SingleNote 音符 + :param _apply_time_division: int 间隔毫秒数 :return list[tuple(int开始时间(毫秒), int乐器, int音符, int力度(内置), float音量(播放)),]""" + if _note.percussive: + return [ + _note, + ] + totalCount = int(_note.duration / _apply_time_division) if totalCount == 0: return [ - (_note.start_time, _note.inst, _note.pitch, _note.velocity, 1), + _note, ] # print(totalCount) - result: List[Tuple[int, int, int, int, float],] = [] + result: List[SingleNote] = [] for _i in range(totalCount): result.append( - ( - _note.start_time + _i * _apply_time_division, - _note.instrument, - _note.pitch, - _note.velocity, - ((totalCount - _i) / totalCount), + SingleNote( + instrument=_note.inst, + pitch=_note.pitch, + velocity=_note.velocity, + startime=int(_note.start_time + _i * (_note.duration / totalCount)), + lastime=int(_note.duration / totalCount), + track_number=_note.track_no, + is_percussion=_note.percussive, ) + # ( + # _note.start_time + _i * _apply_time_division, + # _note.instrument, + # _note.pitch, + # _note.velocity, + # ((totalCount - _i) / totalCount), + # ) ) return result - # 简单的单音填充 - def to_command_list_in_score( - self, - scoreboard_name: str = "mscplay", - max_volume: float = 1.0, - speed: float = 1.0, - ) -> Tuple[List[List[SingleCommand]], int, int]: - """ - 使用金羿的转换思路,使用完全填充算法优化音感后,将midi转换为我的世界命令列表 - - Parameters - ---------- - scoreboard_name: str - 我的世界的计分板名称 - max_volume: float - 最大播放音量,注意:这里的音量范围为(0,1],如果超出将被处理为正确值,其原理为在距离玩家 (1 / volume -1) 的地方播放 - speed: float - 速度,注意:这里的速度指的是播放倍率,其原理为在播放音频的时候,每个音符的播放时间除以 speed - - Returns - ------- - tuple( list[list[SingleCommand指令,... ],... ], int指令数量, int音乐时长游戏刻 ) - """ - - if speed == 0: - raise ZeroSpeedError("播放速度仅可为正实数") - max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume) - - self.to_music_channels() - - note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[]) - InstID = -1 - - # 此处 我们把通道视为音轨 - for i in self.channels.keys(): - # 如果当前通道为空 则跳过 - if not self.channels[i]: - continue - - # nowChannel = [] - for track_no, track in self.channels[i].items(): - noteMsgs = [] - MsgIndex = [] - - for msg in track: - if msg[0] == "PgmC": - InstID = msg[1] - - elif msg[0] == "NoteS": - noteMsgs.append(msg[1:]) - MsgIndex.append(msg[1]) - - elif msg[0] == "NoteE": - if msg[1] in MsgIndex: - note_channels[i].append( - SingleNote( - InstID, - msg[1], - noteMsgs[MsgIndex.index(msg[1])][1], - noteMsgs[MsgIndex.index(msg[1])][2], - msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2], - track_number=track_no, - ) - ) - noteMsgs.pop(MsgIndex.index(msg[1])) - MsgIndex.pop(MsgIndex.index(msg[1])) - - del InstID - - tracks = [] - cmd_amount = 0 - max_score = 0 - - # 此处 我们把通道视为音轨 - for no, track in note_channels.items(): - # 如果当前通道为空 则跳过 - if not track: - continue - - SpecialBits = True if no == 9 else False - - track_now = [] - - for note in track: - for every_note in self._linear_note( - note, 100 if note.track_no == 0 else 500 - ): - soundID, _X = ( - self.perc_inst_to_soundID_withX(note.pitch) - if SpecialBits - else self.inst_to_souldID_withX(note.inst) - ) - - score_now = round(every_note[0] / speed / 50) - - max_score = max(max_score, score_now) - mc_pitch = 2 ** ((note.pitch - 60 - _X) / 12) - blockmeter = ( - 1 - / (1 if note.track_no == 0 else 0.9) - / max_volume - / every_note[4] - - 1 - ) - - track_now.append( - SingleCommand( - self.execute_cmd_head.format( - "@a[scores=({}={})]".format(scoreboard_name, score_now) - .replace("(", r"{") - .replace(")", r"}") - ) - + "playsound {} @s ^ ^ ^{} {} {}".format( - soundID, - blockmeter, - note.velocity / 128, - "" if SpecialBits else mc_pitch, - ), - annotation="在{}播放{}%({}BM)的{}音".format( - mctick2timestr(score_now), - max_volume * 100, - blockmeter, - "{}:{}".format(soundID, note.pitch), - ), - ), - ) - - cmd_amount += 1 - - if track_now: - self.music_command_list.extend(track_now) - tracks.append(track_now) - - self.music_tick_num = max_score - return (tracks, cmd_amount, max_score) - - # 简单的单音填充的延迟应用 def to_command_list_in_delay( self, max_volume: float = 1.0, @@ -336,7 +120,7 @@ class FutureMidiConvertM4(MidiConvert): player_selector: str = "@a", ) -> Tuple[List[SingleCommand], int, int]: """ - 使用金羿的转换思路,使用完全填充算法优化音感后,将midi转换为我的世界命令列表,并输出每个音符之后的延迟 + 将midi转换为我的世界命令列表,并输出每个音符之后的延迟 Parameters ---------- @@ -353,130 +137,48 @@ class FutureMidiConvertM4(MidiConvert): """ if speed == 0: - raise ZeroSpeedError("播放速度仅可为正实数") + raise ZeroSpeedError("播放速度仅可为(0,1]范围内的正实数") max_volume = 1 if max_volume > 1 else (0.001 if max_volume <= 0 else max_volume) - self.to_music_channels() - - note_channels: Dict[int, List[SingleNote]] = empty_midi_channels(staff=[]) - InstID = -1 + notes_list: List[SingleNote] = [] # 此处 我们把通道视为音轨 - for i in self.channels.keys(): - # 如果当前通道为空 则跳过 - if not self.channels[i]: - continue - - # nowChannel = [] - for track_no, track in self.channels[i].items(): - noteMsgs = [] - MsgIndex = [] - - for msg in track: - if msg[0] == "PgmC": - InstID = msg[1] - - elif msg[0] == "NoteS": - noteMsgs.append(msg[1:]) - MsgIndex.append(msg[1]) - - elif msg[0] == "NoteE": - if msg[1] in MsgIndex: - note_channels[i].append( - SingleNote( - InstID, - msg[1], - noteMsgs[MsgIndex.index(msg[1])][1], - noteMsgs[MsgIndex.index(msg[1])][2], - msg[-1] - noteMsgs[MsgIndex.index(msg[1])][2], - track_number=track_no, - ) - ) - noteMsgs.pop(MsgIndex.index(msg[1])) - MsgIndex.pop(MsgIndex.index(msg[1])) - - del InstID - - tracks = {} - InstID = -1 - # open("RES.TXT", "w", encoding="utf-8").write(str(note_channels)) - - # 此处 我们把通道视为音轨 - for no, track in note_channels.items(): - # 如果当前通道为空 则跳过 - if not track: - continue - - SpecialBits = True if no == 9 else False - - for note in track: - liner_list = self._linear_note(note, 100 if note.track_no == 0 else 500) - for every_note in liner_list: - soundID, _X = ( - self.perc_inst_to_soundID_withX(note.pitch) - if SpecialBits - else self.inst_to_souldID_withX(note.inst) - ) - - score_now = round(every_note[0] / speed / 50) - - try: - tracks[score_now].append( - self.execute_cmd_head.format(player_selector) - + f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} " - + ( - "" - if SpecialBits - else f"{2 ** ((note.pitch - 60 - _X) / 12)}" - ) - ) - except KeyError: - tracks[score_now] = [ - self.execute_cmd_head.format(player_selector) - + f"playsound {soundID} @s ^ ^ ^{1 / (1 if note.track_no == 0 else 0.9) / max_volume / every_note[4] - 1} {note.velocity / 128} " - + ( - "" - if SpecialBits - else f"{2 ** ((note.pitch - 60 - _X) / 12)}" - ) - ] - - all_ticks = list(tracks.keys()) - all_ticks.sort() - results: List[SingleCommand] = [] - max_multi = 0 - now_multi_delay = 0 - now_multi = 0 - - for i in range(len(all_ticks)): - l = len(tracks[all_ticks[i]]) - for j in range(l): - results.append( - SingleCommand( - tracks[all_ticks[i]][j], - tick_delay=( - 0 - if j != 0 - else ( - all_ticks[i] - all_ticks[i - 1] - if i != 0 - else all_ticks[i] - ) - ), - annotation="由 音·创 生成", - ) - ) - if results[-1].delay + now_multi_delay <= 1: - now_multi += 1 - now_multi_delay += results[-1].delay + for channel in self.to_music_note_channels().values(): + for note in channel: + if not note.percussive: + notes_list.extend(self._linear_note(note, note.get_mc_pitch * 500)) else: - max_multi = max(max_multi, now_multi) - now_multi = 0 - now_multi_delay = 0 + notes_list.append(note) - self.music_command_list = results - self.music_tick_num = max(all_ticks) - return results, self.music_tick_num, max_multi + notes_list.sort(key=lambda a: a.start_time) + + self.music_command_list = [] + multi = max_multi = 0 + delaytime_previous = 0 + + for note in notes_list: + delaytime_now = round(note.start_time / speed / 50) + if (tickdelay := (delaytime_now - delaytime_previous)) == 0: + multi += 1 + else: + max_multi = max(max_multi, multi) + multi = 0 + self.music_command_list.append( + SingleCommand( + self.execute_cmd_head.format(player_selector) + + note.to_command(max_volume), + tick_delay=tickdelay, + annotation="在{}播放{}%的{}音".format( + mctick2timestr(delaytime_now), + max_volume * 100, + "{}:{}".format(note.mc_sound_ID, note.mc_pitch), + ), + ) + ) + delaytime_previous = delaytime_now + + self.music_tick_num = round(notes_list[-1].start_time / speed / 50) + return self.music_command_list, self.music_tick_num, max_multi + 1 class FutureMidiConvertM5(MidiConvert): diff --git a/Musicreater/main.py b/Musicreater/main.py index 619f51c..a9e3070 100644 --- a/Musicreater/main.py +++ b/Musicreater/main.py @@ -631,7 +631,9 @@ class MidiConvert: .replace("(", r"{") .replace(")", r"}") ) - + note.to_command(max_volume), + + note.to_command( + (max_volume) if note.track_no == 0 else (max_volume * 0.9) + ), annotation="在{}播放{}%的{}音".format( mctick2timestr(score_now), max_volume * 100, @@ -698,7 +700,9 @@ class MidiConvert: self.music_command_list.append( SingleCommand( self.execute_cmd_head.format(player_selector) - + note.to_command(max_volume), + + note.to_command( + (max_volume) if note.track_no == 0 else (max_volume * 0.9) + ), tick_delay=tickdelay, annotation="在{}播放{}%的{}音".format( mctick2timestr(delaytime_now), diff --git a/Musicreater/subclass.py b/Musicreater/subclass.py index c1bf07f..0ca4504 100644 --- a/Musicreater/subclass.py +++ b/Musicreater/subclass.py @@ -21,7 +21,7 @@ from dataclasses import dataclass from typing import Optional from .constants import PERCUSSION_INSTRUMENT_LIST -from .utils import inst_to_souldID_withX, perc_inst_to_soundID_withX +from .utils import inst_to_souldID_withX, perc_inst_to_soundID_withX, volume2distance @dataclass(init=False) @@ -78,8 +78,6 @@ class SingleNote: """音符持续时间 ms""" self.track_no: int = track_number """音符所处的音轨""" - self.track_no: int = track_number - """音符所处的音轨""" self.percussive = ( (is_percussion in PERCUSSION_INSTRUMENT_LIST) @@ -89,31 +87,48 @@ class SingleNote: """是否为打击乐器""" @property - def inst(self): + def inst(self) -> int: """乐器编号""" return self.instrument @inst.setter - def inst(self, inst_): + def inst(self, inst_: int): self.instrument = inst_ @property - def pitch(self): + def pitch(self) -> int: """音符编号""" return self.note - def __str__(self): - return "{}Note(Instrument = {}, {}Velocity = {}, StartTime = {}, Duration = {},)".format( + @property + def get_mc_pitch(self) -> float: + self.mc_sound_ID, _X = ( + perc_inst_to_soundID_withX(self.inst) + if self.percussive + else inst_to_souldID_withX(self.inst) + ) + return -1 if self.percussive else 2 ** ((self.note - 60 - _X) / 12) + + def __str__(self, is_track: bool = False): + return "{}Note(Instrument = {}, {}Velocity = {}, StartTime = {}, Duration = {}{})".format( "Percussive" if self.percussive else "", self.inst, "" if self.percussive else "Pitch = {}, ".format(self.pitch), self.start_time, self.duration, + ", Track = {}".format(self.track_no) if is_track else "", ) def __tuple__(self): return ( - (self.percussive, self.inst, self.velocity, self.start_time, self.duration) + ( + self.percussive, + self.inst, + self.velocity, + self.start_time, + self.duration, + self.track_no, + ) if self.percussive else ( self.percussive, @@ -122,6 +137,7 @@ class SingleNote: self.velocity, self.start_time, self.duration, + self.track_no, ) ) @@ -133,6 +149,7 @@ class SingleNote: "Velocity": self.velocity, "StartTime": self.start_time, "Duration": self.duration, + "Track": self.track_no, } if self.percussive else { @@ -142,6 +159,7 @@ class SingleNote: "Velocity": self.velocity, "StartTime": self.start_time, "Duration": self.duration, + "Track": self.track_no, } ) @@ -165,14 +183,13 @@ class SingleNote: # delaytime_now = round(self.start_time / float(speed) / 50) self.mc_pitch = "" if self.percussive else 2 ** ((self.note - 60 - _X) / 12) - self.mc_distance_volume = 128 / volume_percentage / self.velocity + ( - 1 if self.percussive else self.velocity / 32 - ) + + self.mc_distance_volume = volume2distance(self.velocity * volume_percentage) return "playsound {} @s ^ ^ ^{} {} {}".format( self.mc_sound_ID, self.mc_distance_volume, - self.velocity / 128, + volume_percentage, self.mc_pitch, ) diff --git a/Musicreater/types.py b/Musicreater/types.py index d489ad3..3a09fff 100644 --- a/Musicreater/types.py +++ b/Musicreater/types.py @@ -58,7 +58,7 @@ ChannelType = Dict[ ], ] """ -以字典所标记的频道信息类型(即将弃用) +以字典所标记的频道信息类型(已弃用) Dict[int,Dict[int,List[Union[Tuple[Literal["PgmC"], int, int],Tuple[Literal["NoteS"], int, int, int],Tuple[Literal["NoteE"], int, int],]],],] """ diff --git a/Musicreater/utils.py b/Musicreater/utils.py index cc486db..277b910 100644 --- a/Musicreater/utils.py +++ b/Musicreater/utils.py @@ -15,6 +15,8 @@ Terms & Conditions: License.md in the root directory # Email TriM-Organization@hotmail.com # 若需转载或借鉴 许可声明请查看仓库目录下的 License.md +import math + from .constants import PERCUSSION_INSTRUMENT_TABLE, PITCHED_INSTRUMENT_TABLE from typing import Any, Dict, Tuple @@ -89,3 +91,29 @@ def perc_inst_to_soundID_withX(instrumentID: int) -> Tuple[str, int]: # 明明已经走了 # 凭什么还要在我心里留下缠绵缱绻 + + +def volume2distance(vol: float) -> float: + """ + midi力度值拟合成的距离函数 + + Parameters + ---------- + vol: int + midi音符力度值 + + Returns + ------- + float播放中心到玩家的距离 + """ + return ( + -8.081720684086314 + * math.log( + vol + 14.579508825070013, + ) + + 37.65806375944386 + if vol < 60.64 + else 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649) + + -6.313841334963396 * (vol + 2592.272889454798) + + 4558.496367823575 + ) diff --git a/example_futureFunction.py b/example_futureFunction.py index cf8ea63..0daec0b 100644 --- a/example_futureFunction.py +++ b/example_futureFunction.py @@ -1,13 +1,13 @@ import Musicreater.experiment import Musicreater.plugin -import Musicreater.plugin.addonpack +import Musicreater.plugin.mcstructfile print( - Musicreater.plugin.addonpack.to_addon_pack_in_delay( - Musicreater.experiment.FutureMidiConvertM4.from_midi_file(input("midi路径:"), old_exe_format=False), - Musicreater.plugin.ConvertConfig( - input("输出路径:"), - volume=1 + Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay( + Musicreater.experiment.FutureMidiConvertM4.from_midi_file( + input("midi路径:"), old_exe_format=False ), + Musicreater.plugin.ConvertConfig(input("输出路径:"), volume=1), + max_height=32, ) ) diff --git a/example_mcstructure.py b/example_mcstructure.py index 45a075c..52832ac 100644 --- a/example_mcstructure.py +++ b/example_mcstructure.py @@ -6,5 +6,6 @@ print( Musicreater.plugin.mcstructfile.to_mcstructure_file_in_delay( Musicreater.MidiConvert.from_midi_file(input("midi路径:"), old_exe_format=False), Musicreater.plugin.ConvertConfig(input("输出路径:"), volume=1), + max_height=32, ) ) diff --git a/resources/test/volumn_function_fit.py b/resources/test/volumn_function_fit.py new file mode 100644 index 0000000..8b16b32 --- /dev/null +++ b/resources/test/volumn_function_fit.py @@ -0,0 +1,42 @@ +import numpy as np +from scipy.optimize import curve_fit +import matplotlib.pyplot as plt + + +def q_function1(x, a, a2, c1,): + return a * np.log( x + a2,)+ c1 + +def q_function2(x, b, b2, b3, b4, c2): + return b * ((x + b2) ** b3) + b4 * (x+b2) + c2 + + +x_data = np.array([0, 16, 32, 48, 64, 80, 96, 112, 128]) +y_data = np.array([16, 10, 6.75, 4, 2.5, 1.6, 0.8, 0.3, 0]) + + +p_est1, err_est1 = curve_fit(q_function1, x_data[:5], y_data[:5], maxfev=1000000) +p_est2, err_est2 = curve_fit(q_function2, x_data[4:], y_data[4:], maxfev=1000000) + + +print(q_function1(x_data[:5], *p_est1)) +print(q_function2(x_data[4:], *p_est2)) + +print("参数一:",*p_est1) +print("参数二:",*p_est2) + +# 绘制图像 +plt.plot( + np.arange(0, 64.1, 0.1), q_function1(np.arange(0, 64.1, 0.1), *p_est1), label=r"FIT1" +) +plt.plot( + np.arange(64, 128.1, 0.1), q_function2(np.arange(64, 128.1, 0.1), *p_est2), label=r"FIT2" +) + + +plt.scatter(x_data, y_data, color="red") # 标记给定的点 +# plt.xlabel('x') +# plt.ylabel('y') +plt.title("Function Fit") +plt.legend() +# plt.grid(True) +plt.show() diff --git a/resources/test/volumn_function_test.py b/resources/test/volumn_function_test.py new file mode 100644 index 0000000..2185c95 --- /dev/null +++ b/resources/test/volumn_function_test.py @@ -0,0 +1,36 @@ +import matplotlib.pyplot as plt +import numpy as np + +# 定义对数函数 +def q_function1(vol): + # return -23.65060754864053*((x+508.2130392724084)**0.8433764630986903) + 7.257078620637543 * (x+407.86870598508153) + 1585.6201108739122 + # return -58.863374003875954 *((x+12.41481943150274 )**0.9973316187745871 ) +57.92341268595151 * (x+ 13.391132186222036) + -32.92986286030519 + return -8.081720684086314 * np.log( vol + 14.579508825070013,)+ 37.65806375944386 + + +def q_function2(vol): + return 0.2721359356095803 * ((vol + 2592.272889454798) ** 1.358571233418649) + -6.313841334963396 * (vol + 2592.272889454798) + 4558.496367823575 + +# 生成 x 值 +x_values = np.linspace(0, 128, 1000) + + +x_data = np.array([0,16,32,48,64,80,96,112,128]) +y_data = np.array([16, 10, 6.75, 4, 2.5, 1.6, 0.8, 0.3, 0]) + + +print(q_function1(x_data)) +print(q_function2(x_data)) + +# 绘制图像 +plt.plot(x_values, q_function1(x_values,),label = "fit1") +plt.plot(x_values, q_function2(x_values,),label = "fit2") +plt.scatter(x_data, y_data, color='red') # 标记给定的点 +# plt.scatter(x_data, y_data2, color='green') # 标记给定的点 +# plt.scatter(x_data, y_data3, color='blue') # 标记给定的点 +plt.xlabel('x') +plt.ylabel('y') +plt.title('Function') +plt.legend() +plt.grid(True) +plt.show()