[{"data":1,"prerenderedAt":16492},["ShallowReactive",2],{"series-c-sharp-chiptune":3},[4,2744,5670,8772,11029,14419],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"date":11,"tags":12,"rowTypeId":18,"sitemap":19,"body":20,"_type":2738,"_id":2739,"_source":2740,"_file":2741,"_stem":2742,"_extension":2743},"/articles/tech/blazor/diff-detectable-object","blazor",false,"","ObservableObjectとDiffDetectableObjectで「変更された？」を自動判定する","Blazor WebAssemblyで「保存ボタンを押すべきか」を自動判定する仕組みを、ObservableObjectとDiffDetectableObjectという2つのシンプルなクラスで実装しました。MVVM ToolkitのObservableObjectとの違いや、イベント駆動で状態フラグを同期させる設計のコツを解説します。","2026-04-14",[13,14,15,16,17],"C#","MVVM","Blazor","INotifyPropertyChanged","PICOM",1,{"loc":5,"lastmod":11,"priority":18},{"type":21,"children":22,"toc":2726},"root",[23,31,61,91,119,125,144,695,706,874,881,894,906,968,974,993,1422,1478,1497,1616,1621,1631,1975,1994,2000,2005,2279,2315,2469,2475,2487,2645,2655,2677,2682,2720],{"type":24,"tag":25,"props":26,"children":28},"element","h2",{"id":27},"はじめに",[29],{"type":30,"value":27},"text",{"type":24,"tag":32,"props":33,"children":34},"p",{},[35,37,43,45,52,54,59],{"type":30,"value":36},"楽譜エディタを作っていて、地味に困るのが「",{"type":24,"tag":38,"props":39,"children":40},"strong",{},[41],{"type":30,"value":42},"未保存の変更があるかどうか",{"type":30,"value":44},"」の判定です。タイトルバーに",{"type":24,"tag":46,"props":47,"children":49},"code",{"className":48},[],[50],{"type":30,"value":51},"*",{"type":30,"value":53},"を出したり、保存ボタンの活性を切り替えたり、タブを閉じようとしたら「未保存の変更があります」と警告したい。これをやるには、現在のデータが",{"type":24,"tag":38,"props":55,"children":56},{},[57],{"type":30,"value":58},"初期状態から変更されているか",{"type":30,"value":60},"を常に知っておく必要があります。",{"type":24,"tag":32,"props":62,"children":63},{},[64,66,72,74,80,82,89],{"type":30,"value":65},"PICOMでは、この判定を",{"type":24,"tag":46,"props":67,"children":69},{"className":68},[],[70],{"type":30,"value":71},"ObservableObject",{"type":30,"value":73},"と",{"type":24,"tag":46,"props":75,"children":77},{"className":76},[],[78],{"type":30,"value":79},"DiffDetectableObject\u003CT>",{"type":30,"value":81},"という2つの小さなクラスで実装しました。この記事では、その設計と、",{"type":24,"tag":83,"props":84,"children":86},"tooltip",{"content":85},"Microsoftが公開しているMVVM用のユーティリティライブラリ。ObservableObjectやRelayCommandなどを提供する",[87],{"type":30,"value":88},"MVVM Community Toolkit",{"type":30,"value":90},"との違いについて書きます。",{"type":24,"tag":92,"props":93,"children":94},"summary-box",{},[95],{"type":24,"tag":32,"props":96,"children":97},{},[98,103,105,110,112,117],{"type":24,"tag":46,"props":99,"children":101},{"className":100},[],[102],{"type":30,"value":16},{"type":30,"value":104},"を実装した",{"type":24,"tag":46,"props":106,"children":108},{"className":107},[],[109],{"type":30,"value":71},{"type":30,"value":111},"をベースに、初期スナップショットと現在のオブジェクトを持つ",{"type":24,"tag":46,"props":113,"children":115},{"className":114},[],[116],{"type":30,"value":79},{"type":30,"value":118},"を被せることで、差分の有無をイベントで通知できるようにしました。保存ボタンの活性判定や、変更通知バッジの表示など、UI側は状態フラグをそのままバインドするだけで済みます。",{"type":24,"tag":25,"props":120,"children":122},{"id":121},"observableobject最小限のプロパティ変更通知",[123],{"type":30,"value":124},"ObservableObject：最小限のプロパティ変更通知",{"type":24,"tag":32,"props":126,"children":127},{},[128,130,135,137,142],{"type":30,"value":129},"まずは下敷きとなる",{"type":24,"tag":46,"props":131,"children":133},{"className":132},[],[134],{"type":30,"value":71},{"type":30,"value":136},"です。中身は",{"type":24,"tag":46,"props":138,"children":140},{"className":139},[],[141],{"type":30,"value":16},{"type":30,"value":143},"の実装だけで、めちゃくちゃ小さいです。",{"type":24,"tag":145,"props":146,"children":150},"pre",{"className":147,"code":148,"language":149,"meta":8,"style":8},"language-csharp shiki shiki-themes vitesse-dark","using System.ComponentModel;\nusing System.Runtime.CompilerServices;\n\nnamespace Sample;\n\npublic abstract class ObservableObject : INotifyPropertyChanged\n{\n    public event PropertyChangedEventHandler? PropertyChanged;\n\n    protected void SetProperty\u003CT>(ref T prop, T value, [CallerMemberName] string? name = null)\n    {\n        if (name is null) throw new Exception();\n        prop = value;\n        NotifyPropertyChanged(name);\n    }\n\n    protected void NotifyPropertyChanged(string name)\n    {\n        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));\n    }\n}\n","csharp",[151],{"type":24,"tag":46,"props":152,"children":153},{"__ignoreMap":8},[154,187,221,231,250,258,292,301,335,343,454,463,517,538,561,570,578,612,620,678,686],{"type":24,"tag":155,"props":156,"children":158},"span",{"class":157,"line":18},"line",[159,165,171,177,182],{"type":24,"tag":155,"props":160,"children":162},{"style":161},"--shiki-default:#4D9375",[163],{"type":30,"value":164},"using",{"type":24,"tag":155,"props":166,"children":168},{"style":167},"--shiki-default:#5DA994",[169],{"type":30,"value":170}," System",{"type":24,"tag":155,"props":172,"children":174},{"style":173},"--shiki-default:#666666",[175],{"type":30,"value":176},".",{"type":24,"tag":155,"props":178,"children":179},{"style":167},[180],{"type":30,"value":181},"ComponentModel",{"type":24,"tag":155,"props":183,"children":184},{"style":173},[185],{"type":30,"value":186},";\n",{"type":24,"tag":155,"props":188,"children":190},{"class":157,"line":189},2,[191,195,199,203,208,212,217],{"type":24,"tag":155,"props":192,"children":193},{"style":161},[194],{"type":30,"value":164},{"type":24,"tag":155,"props":196,"children":197},{"style":167},[198],{"type":30,"value":170},{"type":24,"tag":155,"props":200,"children":201},{"style":173},[202],{"type":30,"value":176},{"type":24,"tag":155,"props":204,"children":205},{"style":167},[206],{"type":30,"value":207},"Runtime",{"type":24,"tag":155,"props":209,"children":210},{"style":173},[211],{"type":30,"value":176},{"type":24,"tag":155,"props":213,"children":214},{"style":167},[215],{"type":30,"value":216},"CompilerServices",{"type":24,"tag":155,"props":218,"children":219},{"style":173},[220],{"type":30,"value":186},{"type":24,"tag":155,"props":222,"children":224},{"class":157,"line":223},3,[225],{"type":24,"tag":155,"props":226,"children":228},{"emptyLinePlaceholder":227},true,[229],{"type":30,"value":230},"\n",{"type":24,"tag":155,"props":232,"children":234},{"class":157,"line":233},4,[235,241,246],{"type":24,"tag":155,"props":236,"children":238},{"style":237},"--shiki-default:#CB7676",[239],{"type":30,"value":240},"namespace",{"type":24,"tag":155,"props":242,"children":243},{"style":167},[244],{"type":30,"value":245}," Sample",{"type":24,"tag":155,"props":247,"children":248},{"style":173},[249],{"type":30,"value":186},{"type":24,"tag":155,"props":251,"children":253},{"class":157,"line":252},5,[254],{"type":24,"tag":155,"props":255,"children":256},{"emptyLinePlaceholder":227},[257],{"type":30,"value":230},{"type":24,"tag":155,"props":259,"children":261},{"class":157,"line":260},6,[262,267,272,277,282,287],{"type":24,"tag":155,"props":263,"children":264},{"style":237},[265],{"type":30,"value":266},"public",{"type":24,"tag":155,"props":268,"children":269},{"style":237},[270],{"type":30,"value":271}," abstract",{"type":24,"tag":155,"props":273,"children":274},{"style":237},[275],{"type":30,"value":276}," class",{"type":24,"tag":155,"props":278,"children":279},{"style":167},[280],{"type":30,"value":281}," ObservableObject",{"type":24,"tag":155,"props":283,"children":284},{"style":173},[285],{"type":30,"value":286}," :",{"type":24,"tag":155,"props":288,"children":289},{"style":167},[290],{"type":30,"value":291}," INotifyPropertyChanged\n",{"type":24,"tag":155,"props":293,"children":295},{"class":157,"line":294},7,[296],{"type":24,"tag":155,"props":297,"children":298},{"style":173},[299],{"type":30,"value":300},"{\n",{"type":24,"tag":155,"props":302,"children":304},{"class":157,"line":303},8,[305,310,315,320,325,331],{"type":24,"tag":155,"props":306,"children":307},{"style":237},[308],{"type":30,"value":309},"    public",{"type":24,"tag":155,"props":311,"children":312},{"style":237},[313],{"type":30,"value":314}," event",{"type":24,"tag":155,"props":316,"children":317},{"style":167},[318],{"type":30,"value":319}," PropertyChangedEventHandler",{"type":24,"tag":155,"props":321,"children":322},{"style":173},[323],{"type":30,"value":324},"?",{"type":24,"tag":155,"props":326,"children":328},{"style":327},"--shiki-default:#80A665",[329],{"type":30,"value":330}," PropertyChanged",{"type":24,"tag":155,"props":332,"children":333},{"style":173},[334],{"type":30,"value":186},{"type":24,"tag":155,"props":336,"children":338},{"class":157,"line":337},9,[339],{"type":24,"tag":155,"props":340,"children":341},{"emptyLinePlaceholder":227},[342],{"type":30,"value":230},{"type":24,"tag":155,"props":344,"children":346},{"class":157,"line":345},10,[347,352,357,362,367,372,377,382,387,392,397,401,406,410,415,420,425,430,434,439,444,449],{"type":24,"tag":155,"props":348,"children":349},{"style":237},[350],{"type":30,"value":351},"    protected",{"type":24,"tag":155,"props":353,"children":354},{"style":161},[355],{"type":30,"value":356}," void",{"type":24,"tag":155,"props":358,"children":359},{"style":327},[360],{"type":30,"value":361}," SetProperty",{"type":24,"tag":155,"props":363,"children":364},{"style":173},[365],{"type":30,"value":366},"\u003C",{"type":24,"tag":155,"props":368,"children":369},{"style":167},[370],{"type":30,"value":371},"T",{"type":24,"tag":155,"props":373,"children":374},{"style":173},[375],{"type":30,"value":376},">(",{"type":24,"tag":155,"props":378,"children":379},{"style":237},[380],{"type":30,"value":381},"ref",{"type":24,"tag":155,"props":383,"children":384},{"style":167},[385],{"type":30,"value":386}," T",{"type":24,"tag":155,"props":388,"children":389},{"style":327},[390],{"type":30,"value":391}," prop",{"type":24,"tag":155,"props":393,"children":394},{"style":173},[395],{"type":30,"value":396},",",{"type":24,"tag":155,"props":398,"children":399},{"style":167},[400],{"type":30,"value":386},{"type":24,"tag":155,"props":402,"children":403},{"style":327},[404],{"type":30,"value":405}," value",{"type":24,"tag":155,"props":407,"children":408},{"style":173},[409],{"type":30,"value":396},{"type":24,"tag":155,"props":411,"children":412},{"style":173},[413],{"type":30,"value":414}," [",{"type":24,"tag":155,"props":416,"children":417},{"style":167},[418],{"type":30,"value":419},"CallerMemberName",{"type":24,"tag":155,"props":421,"children":422},{"style":173},[423],{"type":30,"value":424},"]",{"type":24,"tag":155,"props":426,"children":427},{"style":161},[428],{"type":30,"value":429}," string",{"type":24,"tag":155,"props":431,"children":432},{"style":173},[433],{"type":30,"value":324},{"type":24,"tag":155,"props":435,"children":436},{"style":327},[437],{"type":30,"value":438}," name",{"type":24,"tag":155,"props":440,"children":441},{"style":173},[442],{"type":30,"value":443}," =",{"type":24,"tag":155,"props":445,"children":446},{"style":237},[447],{"type":30,"value":448}," null",{"type":24,"tag":155,"props":450,"children":451},{"style":173},[452],{"type":30,"value":453},")\n",{"type":24,"tag":155,"props":455,"children":457},{"class":157,"line":456},11,[458],{"type":24,"tag":155,"props":459,"children":460},{"style":173},[461],{"type":30,"value":462},"    {\n",{"type":24,"tag":155,"props":464,"children":466},{"class":157,"line":465},12,[467,472,477,483,488,492,497,502,507,512],{"type":24,"tag":155,"props":468,"children":469},{"style":161},[470],{"type":30,"value":471},"        if",{"type":24,"tag":155,"props":473,"children":474},{"style":173},[475],{"type":30,"value":476}," (",{"type":24,"tag":155,"props":478,"children":480},{"style":479},"--shiki-default:#BD976A",[481],{"type":30,"value":482},"name",{"type":24,"tag":155,"props":484,"children":485},{"style":237},[486],{"type":30,"value":487}," is",{"type":24,"tag":155,"props":489,"children":490},{"style":237},[491],{"type":30,"value":448},{"type":24,"tag":155,"props":493,"children":494},{"style":173},[495],{"type":30,"value":496},")",{"type":24,"tag":155,"props":498,"children":499},{"style":161},[500],{"type":30,"value":501}," throw",{"type":24,"tag":155,"props":503,"children":504},{"style":237},[505],{"type":30,"value":506}," new",{"type":24,"tag":155,"props":508,"children":509},{"style":167},[510],{"type":30,"value":511}," Exception",{"type":24,"tag":155,"props":513,"children":514},{"style":173},[515],{"type":30,"value":516},"();\n",{"type":24,"tag":155,"props":518,"children":520},{"class":157,"line":519},13,[521,526,530,534],{"type":24,"tag":155,"props":522,"children":523},{"style":479},[524],{"type":30,"value":525},"        prop",{"type":24,"tag":155,"props":527,"children":528},{"style":173},[529],{"type":30,"value":443},{"type":24,"tag":155,"props":531,"children":532},{"style":479},[533],{"type":30,"value":405},{"type":24,"tag":155,"props":535,"children":536},{"style":173},[537],{"type":30,"value":186},{"type":24,"tag":155,"props":539,"children":541},{"class":157,"line":540},14,[542,547,552,556],{"type":24,"tag":155,"props":543,"children":544},{"style":327},[545],{"type":30,"value":546},"        NotifyPropertyChanged",{"type":24,"tag":155,"props":548,"children":549},{"style":173},[550],{"type":30,"value":551},"(",{"type":24,"tag":155,"props":553,"children":554},{"style":479},[555],{"type":30,"value":482},{"type":24,"tag":155,"props":557,"children":558},{"style":173},[559],{"type":30,"value":560},");\n",{"type":24,"tag":155,"props":562,"children":564},{"class":157,"line":563},15,[565],{"type":24,"tag":155,"props":566,"children":567},{"style":173},[568],{"type":30,"value":569},"    }\n",{"type":24,"tag":155,"props":571,"children":573},{"class":157,"line":572},16,[574],{"type":24,"tag":155,"props":575,"children":576},{"emptyLinePlaceholder":227},[577],{"type":30,"value":230},{"type":24,"tag":155,"props":579,"children":581},{"class":157,"line":580},17,[582,586,590,595,599,604,608],{"type":24,"tag":155,"props":583,"children":584},{"style":237},[585],{"type":30,"value":351},{"type":24,"tag":155,"props":587,"children":588},{"style":161},[589],{"type":30,"value":356},{"type":24,"tag":155,"props":591,"children":592},{"style":327},[593],{"type":30,"value":594}," NotifyPropertyChanged",{"type":24,"tag":155,"props":596,"children":597},{"style":173},[598],{"type":30,"value":551},{"type":24,"tag":155,"props":600,"children":601},{"style":161},[602],{"type":30,"value":603},"string",{"type":24,"tag":155,"props":605,"children":606},{"style":327},[607],{"type":30,"value":438},{"type":24,"tag":155,"props":609,"children":610},{"style":173},[611],{"type":30,"value":453},{"type":24,"tag":155,"props":613,"children":615},{"class":157,"line":614},18,[616],{"type":24,"tag":155,"props":617,"children":618},{"style":173},[619],{"type":30,"value":462},{"type":24,"tag":155,"props":621,"children":623},{"class":157,"line":622},19,[624,629,633,637,642,646,652,656,660,665,669,673],{"type":24,"tag":155,"props":625,"children":626},{"style":479},[627],{"type":30,"value":628},"        PropertyChanged",{"type":24,"tag":155,"props":630,"children":631},{"style":237},[632],{"type":30,"value":324},{"type":24,"tag":155,"props":634,"children":635},{"style":173},[636],{"type":30,"value":176},{"type":24,"tag":155,"props":638,"children":639},{"style":327},[640],{"type":30,"value":641},"Invoke",{"type":24,"tag":155,"props":643,"children":644},{"style":173},[645],{"type":30,"value":551},{"type":24,"tag":155,"props":647,"children":649},{"style":648},"--shiki-default:#C99076",[650],{"type":30,"value":651},"this",{"type":24,"tag":155,"props":653,"children":654},{"style":173},[655],{"type":30,"value":396},{"type":24,"tag":155,"props":657,"children":658},{"style":237},[659],{"type":30,"value":506},{"type":24,"tag":155,"props":661,"children":662},{"style":167},[663],{"type":30,"value":664}," PropertyChangedEventArgs",{"type":24,"tag":155,"props":666,"children":667},{"style":173},[668],{"type":30,"value":551},{"type":24,"tag":155,"props":670,"children":671},{"style":479},[672],{"type":30,"value":482},{"type":24,"tag":155,"props":674,"children":675},{"style":173},[676],{"type":30,"value":677},"));\n",{"type":24,"tag":155,"props":679,"children":681},{"class":157,"line":680},20,[682],{"type":24,"tag":155,"props":683,"children":684},{"style":173},[685],{"type":30,"value":569},{"type":24,"tag":155,"props":687,"children":689},{"class":157,"line":688},21,[690],{"type":24,"tag":155,"props":691,"children":692},{"style":173},[693],{"type":30,"value":694},"}\n",{"type":24,"tag":32,"props":696,"children":697},{},[698,704],{"type":24,"tag":46,"props":699,"children":701},{"className":700},[],[702],{"type":30,"value":703},"[CallerMemberName]",{"type":30,"value":705},"でプロパティ名を自動取得しているので、派生クラスでは次のように書けます。",{"type":24,"tag":145,"props":707,"children":709},{"className":147,"code":708,"language":149,"meta":8,"style":8},"public class MusicMetadata : ObservableObject\n{\n    private string _title = \"\";\n    public string Title\n    {\n        get => _title;\n        set => SetProperty(ref _title, value);\n    }\n}\n",[710],{"type":24,"tag":46,"props":711,"children":712},{"__ignoreMap":8},[713,738,745,776,792,799,820,860,867],{"type":24,"tag":155,"props":714,"children":715},{"class":157,"line":18},[716,720,724,729,733],{"type":24,"tag":155,"props":717,"children":718},{"style":237},[719],{"type":30,"value":266},{"type":24,"tag":155,"props":721,"children":722},{"style":237},[723],{"type":30,"value":276},{"type":24,"tag":155,"props":725,"children":726},{"style":167},[727],{"type":30,"value":728}," MusicMetadata",{"type":24,"tag":155,"props":730,"children":731},{"style":173},[732],{"type":30,"value":286},{"type":24,"tag":155,"props":734,"children":735},{"style":167},[736],{"type":30,"value":737}," ObservableObject\n",{"type":24,"tag":155,"props":739,"children":740},{"class":157,"line":189},[741],{"type":24,"tag":155,"props":742,"children":743},{"style":173},[744],{"type":30,"value":300},{"type":24,"tag":155,"props":746,"children":747},{"class":157,"line":223},[748,753,757,762,766,772],{"type":24,"tag":155,"props":749,"children":750},{"style":237},[751],{"type":30,"value":752},"    private",{"type":24,"tag":155,"props":754,"children":755},{"style":161},[756],{"type":30,"value":429},{"type":24,"tag":155,"props":758,"children":759},{"style":327},[760],{"type":30,"value":761}," _title",{"type":24,"tag":155,"props":763,"children":764},{"style":173},[765],{"type":30,"value":443},{"type":24,"tag":155,"props":767,"children":769},{"style":768},"--shiki-default:#C98A7D77",[770],{"type":30,"value":771}," \"\"",{"type":24,"tag":155,"props":773,"children":774},{"style":173},[775],{"type":30,"value":186},{"type":24,"tag":155,"props":777,"children":778},{"class":157,"line":233},[779,783,787],{"type":24,"tag":155,"props":780,"children":781},{"style":237},[782],{"type":30,"value":309},{"type":24,"tag":155,"props":784,"children":785},{"style":161},[786],{"type":30,"value":429},{"type":24,"tag":155,"props":788,"children":789},{"style":327},[790],{"type":30,"value":791}," Title\n",{"type":24,"tag":155,"props":793,"children":794},{"class":157,"line":252},[795],{"type":24,"tag":155,"props":796,"children":797},{"style":173},[798],{"type":30,"value":462},{"type":24,"tag":155,"props":800,"children":801},{"class":157,"line":260},[802,807,812,816],{"type":24,"tag":155,"props":803,"children":804},{"style":237},[805],{"type":30,"value":806},"        get",{"type":24,"tag":155,"props":808,"children":809},{"style":237},[810],{"type":30,"value":811}," =>",{"type":24,"tag":155,"props":813,"children":814},{"style":479},[815],{"type":30,"value":761},{"type":24,"tag":155,"props":817,"children":818},{"style":173},[819],{"type":30,"value":186},{"type":24,"tag":155,"props":821,"children":822},{"class":157,"line":294},[823,828,832,836,840,844,848,852,856],{"type":24,"tag":155,"props":824,"children":825},{"style":237},[826],{"type":30,"value":827},"        set",{"type":24,"tag":155,"props":829,"children":830},{"style":237},[831],{"type":30,"value":811},{"type":24,"tag":155,"props":833,"children":834},{"style":327},[835],{"type":30,"value":361},{"type":24,"tag":155,"props":837,"children":838},{"style":173},[839],{"type":30,"value":551},{"type":24,"tag":155,"props":841,"children":842},{"style":237},[843],{"type":30,"value":381},{"type":24,"tag":155,"props":845,"children":846},{"style":479},[847],{"type":30,"value":761},{"type":24,"tag":155,"props":849,"children":850},{"style":173},[851],{"type":30,"value":396},{"type":24,"tag":155,"props":853,"children":854},{"style":479},[855],{"type":30,"value":405},{"type":24,"tag":155,"props":857,"children":858},{"style":173},[859],{"type":30,"value":560},{"type":24,"tag":155,"props":861,"children":862},{"class":157,"line":303},[863],{"type":24,"tag":155,"props":864,"children":865},{"style":173},[866],{"type":30,"value":569},{"type":24,"tag":155,"props":868,"children":869},{"class":157,"line":337},[870],{"type":24,"tag":155,"props":871,"children":872},{"style":173},[873],{"type":30,"value":694},{"type":24,"tag":875,"props":876,"children":878},"h3",{"id":877},"mvvm-community-toolkit-ではダメなのか",[879],{"type":30,"value":880},"MVVM Community Toolkit ではダメなのか",{"type":24,"tag":32,"props":882,"children":883},{},[884,886,892],{"type":30,"value":885},"同じことはMVVM Community Toolkitの",{"type":24,"tag":46,"props":887,"children":889},{"className":888},[],[890],{"type":30,"value":891},"[ObservableProperty]",{"type":30,"value":893},"でソースジェネレータ任せにできます。実際、本格的な.NETアプリならそれを使う方が記述量は少なくなります。",{"type":24,"tag":32,"props":895,"children":896},{},[897,899,904],{"type":30,"value":898},"PICOMでToolkitを使わなかった理由は、この程度であれば自前実装した方が良いと判断したからです。実際、PICOMの",{"type":24,"tag":46,"props":900,"children":902},{"className":901},[],[903],{"type":30,"value":71},{"type":30,"value":905},"は20行程度で書き切れています。",{"type":24,"tag":907,"props":908,"children":911},"tradeoff-box",{"cons-label":909,"pros-label":910},"デメリット","メリット",[912,936],{"type":24,"tag":913,"props":914,"children":915},"template",{"v-slot:pros":8},[916],{"type":24,"tag":917,"props":918,"children":919},"ul",{},[920,926,931],{"type":24,"tag":921,"props":922,"children":923},"li",{},[924],{"type":30,"value":925},"依存がゼロ（Blazor WASMのバイナリサイズに寄与しない）",{"type":24,"tag":921,"props":927,"children":928},{},[929],{"type":30,"value":930},"派生クラスから見たAPIも自分で設計できる",{"type":24,"tag":921,"props":932,"children":933},{},[934],{"type":30,"value":935},"ライブラリの管理コスト削減",{"type":24,"tag":913,"props":937,"children":938},{"v-slot:cons":8},[939,950,955],{"type":24,"tag":921,"props":940,"children":941},{},[942,948],{"type":24,"tag":46,"props":943,"children":945},{"className":944},[],[946],{"type":30,"value":947},"SetProperty",{"type":30,"value":949},"を手動で呼ぶ分、記述が冗長になる",{"type":24,"tag":921,"props":951,"children":952},{},[953],{"type":30,"value":954},"比較ロジック（変更が実質あったかどうか）を自前で書く必要がある",{"type":24,"tag":921,"props":956,"children":957},{},[958,960],{"type":30,"value":959},"機能を足すと結局Toolkitと似た形に寄ってくる\n",{"type":24,"tag":917,"props":961,"children":962},{},[963],{"type":24,"tag":921,"props":964,"children":965},{},[966],{"type":30,"value":967},"あくまで低機能前提",{"type":24,"tag":25,"props":969,"children":971},{"id":970},"diffdetectableobject差分の有無をイベントで通知する",[972],{"type":30,"value":973},"DiffDetectableObject：差分の有無をイベントで通知する",{"type":24,"tag":32,"props":975,"children":976},{},[977,979,984,986,991],{"type":30,"value":978},"本題の",{"type":24,"tag":46,"props":980,"children":982},{"className":981},[],[983],{"type":30,"value":79},{"type":30,"value":985},"は、",{"type":24,"tag":46,"props":987,"children":989},{"className":988},[],[990],{"type":30,"value":71},{"type":30,"value":992},"を2つ（初期スナップショットと編集中のもの）保持するラッパーです。",{"type":24,"tag":145,"props":994,"children":996},{"className":147,"code":995,"language":149,"meta":8,"style":8},"public class DiffDetectableObject\u003CT> where T : ObservableObject, IDifferenceComparable\u003CT>\n{\n    public delegate void DiffStateChangedEventHandler(T editable, bool hasDiff);\n    public event DiffStateChangedEventHandler DiffStateChanged;\n\n    public bool HasDiff { get; private set; } = false;\n\n    private T _initObj;\n    private T _editableObj;\n\n    public DiffDetectableObject(T init, T editable)\n    {\n        _initObj = init;\n        _editableObj = editable;\n        _editableObj.PropertyChanged += Editable_PropertyChanged;\n    }\n\n    // ...\n}\n",[997],{"type":24,"tag":46,"props":998,"children":999},{"__ignoreMap":8},[1000,1068,1075,1127,1151,1158,1221,1228,1248,1268,1275,1315,1322,1342,1362,1392,1399,1406,1415],{"type":24,"tag":155,"props":1001,"children":1002},{"class":157,"line":18},[1003,1007,1011,1016,1020,1024,1029,1034,1038,1042,1046,1050,1055,1059,1063],{"type":24,"tag":155,"props":1004,"children":1005},{"style":237},[1006],{"type":30,"value":266},{"type":24,"tag":155,"props":1008,"children":1009},{"style":237},[1010],{"type":30,"value":276},{"type":24,"tag":155,"props":1012,"children":1013},{"style":167},[1014],{"type":30,"value":1015}," DiffDetectableObject",{"type":24,"tag":155,"props":1017,"children":1018},{"style":173},[1019],{"type":30,"value":366},{"type":24,"tag":155,"props":1021,"children":1022},{"style":167},[1023],{"type":30,"value":371},{"type":24,"tag":155,"props":1025,"children":1026},{"style":173},[1027],{"type":30,"value":1028},">",{"type":24,"tag":155,"props":1030,"children":1031},{"style":237},[1032],{"type":30,"value":1033}," where",{"type":24,"tag":155,"props":1035,"children":1036},{"style":167},[1037],{"type":30,"value":386},{"type":24,"tag":155,"props":1039,"children":1040},{"style":173},[1041],{"type":30,"value":286},{"type":24,"tag":155,"props":1043,"children":1044},{"style":167},[1045],{"type":30,"value":281},{"type":24,"tag":155,"props":1047,"children":1048},{"style":173},[1049],{"type":30,"value":396},{"type":24,"tag":155,"props":1051,"children":1052},{"style":167},[1053],{"type":30,"value":1054}," IDifferenceComparable",{"type":24,"tag":155,"props":1056,"children":1057},{"style":173},[1058],{"type":30,"value":366},{"type":24,"tag":155,"props":1060,"children":1061},{"style":167},[1062],{"type":30,"value":371},{"type":24,"tag":155,"props":1064,"children":1065},{"style":173},[1066],{"type":30,"value":1067},">\n",{"type":24,"tag":155,"props":1069,"children":1070},{"class":157,"line":189},[1071],{"type":24,"tag":155,"props":1072,"children":1073},{"style":173},[1074],{"type":30,"value":300},{"type":24,"tag":155,"props":1076,"children":1077},{"class":157,"line":223},[1078,1082,1087,1091,1096,1100,1104,1109,1113,1118,1123],{"type":24,"tag":155,"props":1079,"children":1080},{"style":237},[1081],{"type":30,"value":309},{"type":24,"tag":155,"props":1083,"children":1084},{"style":237},[1085],{"type":30,"value":1086}," delegate",{"type":24,"tag":155,"props":1088,"children":1089},{"style":161},[1090],{"type":30,"value":356},{"type":24,"tag":155,"props":1092,"children":1093},{"style":167},[1094],{"type":30,"value":1095}," DiffStateChangedEventHandler",{"type":24,"tag":155,"props":1097,"children":1098},{"style":173},[1099],{"type":30,"value":551},{"type":24,"tag":155,"props":1101,"children":1102},{"style":167},[1103],{"type":30,"value":371},{"type":24,"tag":155,"props":1105,"children":1106},{"style":327},[1107],{"type":30,"value":1108}," editable",{"type":24,"tag":155,"props":1110,"children":1111},{"style":173},[1112],{"type":30,"value":396},{"type":24,"tag":155,"props":1114,"children":1115},{"style":161},[1116],{"type":30,"value":1117}," bool",{"type":24,"tag":155,"props":1119,"children":1120},{"style":327},[1121],{"type":30,"value":1122}," hasDiff",{"type":24,"tag":155,"props":1124,"children":1125},{"style":173},[1126],{"type":30,"value":560},{"type":24,"tag":155,"props":1128,"children":1129},{"class":157,"line":233},[1130,1134,1138,1142,1147],{"type":24,"tag":155,"props":1131,"children":1132},{"style":237},[1133],{"type":30,"value":309},{"type":24,"tag":155,"props":1135,"children":1136},{"style":237},[1137],{"type":30,"value":314},{"type":24,"tag":155,"props":1139,"children":1140},{"style":167},[1141],{"type":30,"value":1095},{"type":24,"tag":155,"props":1143,"children":1144},{"style":327},[1145],{"type":30,"value":1146}," DiffStateChanged",{"type":24,"tag":155,"props":1148,"children":1149},{"style":173},[1150],{"type":30,"value":186},{"type":24,"tag":155,"props":1152,"children":1153},{"class":157,"line":252},[1154],{"type":24,"tag":155,"props":1155,"children":1156},{"emptyLinePlaceholder":227},[1157],{"type":30,"value":230},{"type":24,"tag":155,"props":1159,"children":1160},{"class":157,"line":260},[1161,1165,1169,1174,1179,1184,1189,1194,1199,1203,1208,1212,1217],{"type":24,"tag":155,"props":1162,"children":1163},{"style":237},[1164],{"type":30,"value":309},{"type":24,"tag":155,"props":1166,"children":1167},{"style":161},[1168],{"type":30,"value":1117},{"type":24,"tag":155,"props":1170,"children":1171},{"style":327},[1172],{"type":30,"value":1173}," HasDiff",{"type":24,"tag":155,"props":1175,"children":1176},{"style":173},[1177],{"type":30,"value":1178}," {",{"type":24,"tag":155,"props":1180,"children":1181},{"style":237},[1182],{"type":30,"value":1183}," get",{"type":24,"tag":155,"props":1185,"children":1186},{"style":173},[1187],{"type":30,"value":1188},";",{"type":24,"tag":155,"props":1190,"children":1191},{"style":237},[1192],{"type":30,"value":1193}," private",{"type":24,"tag":155,"props":1195,"children":1196},{"style":237},[1197],{"type":30,"value":1198}," set",{"type":24,"tag":155,"props":1200,"children":1201},{"style":173},[1202],{"type":30,"value":1188},{"type":24,"tag":155,"props":1204,"children":1205},{"style":173},[1206],{"type":30,"value":1207}," }",{"type":24,"tag":155,"props":1209,"children":1210},{"style":173},[1211],{"type":30,"value":443},{"type":24,"tag":155,"props":1213,"children":1214},{"style":161},[1215],{"type":30,"value":1216}," false",{"type":24,"tag":155,"props":1218,"children":1219},{"style":173},[1220],{"type":30,"value":186},{"type":24,"tag":155,"props":1222,"children":1223},{"class":157,"line":294},[1224],{"type":24,"tag":155,"props":1225,"children":1226},{"emptyLinePlaceholder":227},[1227],{"type":30,"value":230},{"type":24,"tag":155,"props":1229,"children":1230},{"class":157,"line":303},[1231,1235,1239,1244],{"type":24,"tag":155,"props":1232,"children":1233},{"style":237},[1234],{"type":30,"value":752},{"type":24,"tag":155,"props":1236,"children":1237},{"style":167},[1238],{"type":30,"value":386},{"type":24,"tag":155,"props":1240,"children":1241},{"style":327},[1242],{"type":30,"value":1243}," _initObj",{"type":24,"tag":155,"props":1245,"children":1246},{"style":173},[1247],{"type":30,"value":186},{"type":24,"tag":155,"props":1249,"children":1250},{"class":157,"line":337},[1251,1255,1259,1264],{"type":24,"tag":155,"props":1252,"children":1253},{"style":237},[1254],{"type":30,"value":752},{"type":24,"tag":155,"props":1256,"children":1257},{"style":167},[1258],{"type":30,"value":386},{"type":24,"tag":155,"props":1260,"children":1261},{"style":327},[1262],{"type":30,"value":1263}," _editableObj",{"type":24,"tag":155,"props":1265,"children":1266},{"style":173},[1267],{"type":30,"value":186},{"type":24,"tag":155,"props":1269,"children":1270},{"class":157,"line":345},[1271],{"type":24,"tag":155,"props":1272,"children":1273},{"emptyLinePlaceholder":227},[1274],{"type":30,"value":230},{"type":24,"tag":155,"props":1276,"children":1277},{"class":157,"line":456},[1278,1282,1286,1290,1294,1299,1303,1307,1311],{"type":24,"tag":155,"props":1279,"children":1280},{"style":237},[1281],{"type":30,"value":309},{"type":24,"tag":155,"props":1283,"children":1284},{"style":327},[1285],{"type":30,"value":1015},{"type":24,"tag":155,"props":1287,"children":1288},{"style":173},[1289],{"type":30,"value":551},{"type":24,"tag":155,"props":1291,"children":1292},{"style":167},[1293],{"type":30,"value":371},{"type":24,"tag":155,"props":1295,"children":1296},{"style":327},[1297],{"type":30,"value":1298}," init",{"type":24,"tag":155,"props":1300,"children":1301},{"style":173},[1302],{"type":30,"value":396},{"type":24,"tag":155,"props":1304,"children":1305},{"style":167},[1306],{"type":30,"value":386},{"type":24,"tag":155,"props":1308,"children":1309},{"style":327},[1310],{"type":30,"value":1108},{"type":24,"tag":155,"props":1312,"children":1313},{"style":173},[1314],{"type":30,"value":453},{"type":24,"tag":155,"props":1316,"children":1317},{"class":157,"line":465},[1318],{"type":24,"tag":155,"props":1319,"children":1320},{"style":173},[1321],{"type":30,"value":462},{"type":24,"tag":155,"props":1323,"children":1324},{"class":157,"line":519},[1325,1330,1334,1338],{"type":24,"tag":155,"props":1326,"children":1327},{"style":479},[1328],{"type":30,"value":1329},"        _initObj",{"type":24,"tag":155,"props":1331,"children":1332},{"style":173},[1333],{"type":30,"value":443},{"type":24,"tag":155,"props":1335,"children":1336},{"style":479},[1337],{"type":30,"value":1298},{"type":24,"tag":155,"props":1339,"children":1340},{"style":173},[1341],{"type":30,"value":186},{"type":24,"tag":155,"props":1343,"children":1344},{"class":157,"line":540},[1345,1350,1354,1358],{"type":24,"tag":155,"props":1346,"children":1347},{"style":479},[1348],{"type":30,"value":1349},"        _editableObj",{"type":24,"tag":155,"props":1351,"children":1352},{"style":173},[1353],{"type":30,"value":443},{"type":24,"tag":155,"props":1355,"children":1356},{"style":479},[1357],{"type":30,"value":1108},{"type":24,"tag":155,"props":1359,"children":1360},{"style":173},[1361],{"type":30,"value":186},{"type":24,"tag":155,"props":1363,"children":1364},{"class":157,"line":563},[1365,1369,1373,1378,1383,1388],{"type":24,"tag":155,"props":1366,"children":1367},{"style":479},[1368],{"type":30,"value":1349},{"type":24,"tag":155,"props":1370,"children":1371},{"style":173},[1372],{"type":30,"value":176},{"type":24,"tag":155,"props":1374,"children":1375},{"style":479},[1376],{"type":30,"value":1377},"PropertyChanged",{"type":24,"tag":155,"props":1379,"children":1380},{"style":237},[1381],{"type":30,"value":1382}," +=",{"type":24,"tag":155,"props":1384,"children":1385},{"style":479},[1386],{"type":30,"value":1387}," Editable_PropertyChanged",{"type":24,"tag":155,"props":1389,"children":1390},{"style":173},[1391],{"type":30,"value":186},{"type":24,"tag":155,"props":1393,"children":1394},{"class":157,"line":572},[1395],{"type":24,"tag":155,"props":1396,"children":1397},{"style":173},[1398],{"type":30,"value":569},{"type":24,"tag":155,"props":1400,"children":1401},{"class":157,"line":580},[1402],{"type":24,"tag":155,"props":1403,"children":1404},{"emptyLinePlaceholder":227},[1405],{"type":30,"value":230},{"type":24,"tag":155,"props":1407,"children":1408},{"class":157,"line":614},[1409],{"type":24,"tag":155,"props":1410,"children":1412},{"style":1411},"--shiki-default:#758575DD",[1413],{"type":30,"value":1414},"    // ...\n",{"type":24,"tag":155,"props":1416,"children":1417},{"class":157,"line":622},[1418],{"type":24,"tag":155,"props":1419,"children":1420},{"style":173},[1421],{"type":30,"value":694},{"type":24,"tag":32,"props":1423,"children":1424},{},[1425,1427,1433,1434,1440,1442,1447,1449,1454,1456,1461,1463,1469,1471,1476],{"type":30,"value":1426},"肝は",{"type":24,"tag":46,"props":1428,"children":1430},{"className":1429},[],[1431],{"type":30,"value":1432},"_initObj",{"type":30,"value":73},{"type":24,"tag":46,"props":1435,"children":1437},{"className":1436},[],[1438],{"type":30,"value":1439},"_editableObj",{"type":30,"value":1441},"の2本持ちです。",{"type":24,"tag":46,"props":1443,"children":1445},{"className":1444},[],[1446],{"type":30,"value":1432},{"type":30,"value":1448},"は「最後に保存した時点のデータ」、",{"type":24,"tag":46,"props":1450,"children":1452},{"className":1451},[],[1453],{"type":30,"value":1439},{"type":30,"value":1455},"は「編集中のデータ」を指し、どちらも同じ型",{"type":24,"tag":46,"props":1457,"children":1459},{"className":1458},[],[1460],{"type":30,"value":371},{"type":30,"value":1462},"で持ちます。",{"type":24,"tag":46,"props":1464,"children":1466},{"className":1465},[],[1467],{"type":30,"value":1468},"where T : ObservableObject, IDifferenceComparable\u003CT>",{"type":30,"value":1470},"という型制約で、",{"type":24,"tag":46,"props":1472,"children":1474},{"className":1473},[],[1475],{"type":30,"value":371},{"type":30,"value":1477},"は必ず「変更通知できる」「比較できる」の2つを満たすことを強制しています。",{"type":24,"tag":32,"props":1479,"children":1480},{},[1481,1487,1489,1495],{"type":24,"tag":46,"props":1482,"children":1484},{"className":1483},[],[1485],{"type":30,"value":1486},"IDifferenceComparable\u003CT>",{"type":30,"value":1488},"は自前の最小インターフェースで、中身は次のように",{"type":24,"tag":46,"props":1490,"children":1492},{"className":1491},[],[1493],{"type":30,"value":1494},"CompareDifference",{"type":30,"value":1496},"メソッド1本だけです。",{"type":24,"tag":145,"props":1498,"children":1500},{"className":147,"code":1499,"language":149,"meta":8,"style":8},"public interface IDifferenceComparable\u003CTSelf>\n{\n    /// \u003Csummary>差分がある場合:true\u003C/summary>\n    bool CompareDifference(TSelf other);\n}\n",[1501],{"type":24,"tag":46,"props":1502,"children":1503},{"__ignoreMap":8},[1504,1533,1540,1579,1609],{"type":24,"tag":155,"props":1505,"children":1506},{"class":157,"line":18},[1507,1511,1516,1520,1524,1529],{"type":24,"tag":155,"props":1508,"children":1509},{"style":237},[1510],{"type":30,"value":266},{"type":24,"tag":155,"props":1512,"children":1513},{"style":237},[1514],{"type":30,"value":1515}," interface",{"type":24,"tag":155,"props":1517,"children":1518},{"style":167},[1519],{"type":30,"value":1054},{"type":24,"tag":155,"props":1521,"children":1522},{"style":173},[1523],{"type":30,"value":366},{"type":24,"tag":155,"props":1525,"children":1526},{"style":167},[1527],{"type":30,"value":1528},"TSelf",{"type":24,"tag":155,"props":1530,"children":1531},{"style":173},[1532],{"type":30,"value":1067},{"type":24,"tag":155,"props":1534,"children":1535},{"class":157,"line":189},[1536],{"type":24,"tag":155,"props":1537,"children":1538},{"style":173},[1539],{"type":30,"value":300},{"type":24,"tag":155,"props":1541,"children":1542},{"class":157,"line":223},[1543,1548,1552,1557,1561,1566,1571,1575],{"type":24,"tag":155,"props":1544,"children":1545},{"style":1411},[1546],{"type":30,"value":1547},"    /// ",{"type":24,"tag":155,"props":1549,"children":1550},{"style":173},[1551],{"type":30,"value":366},{"type":24,"tag":155,"props":1553,"children":1554},{"style":161},[1555],{"type":30,"value":1556},"summary",{"type":24,"tag":155,"props":1558,"children":1559},{"style":173},[1560],{"type":30,"value":1028},{"type":24,"tag":155,"props":1562,"children":1563},{"style":1411},[1564],{"type":30,"value":1565},"差分がある場合:true",{"type":24,"tag":155,"props":1567,"children":1568},{"style":173},[1569],{"type":30,"value":1570},"\u003C/",{"type":24,"tag":155,"props":1572,"children":1573},{"style":161},[1574],{"type":30,"value":1556},{"type":24,"tag":155,"props":1576,"children":1577},{"style":173},[1578],{"type":30,"value":1067},{"type":24,"tag":155,"props":1580,"children":1581},{"class":157,"line":233},[1582,1587,1592,1596,1600,1605],{"type":24,"tag":155,"props":1583,"children":1584},{"style":161},[1585],{"type":30,"value":1586},"    bool",{"type":24,"tag":155,"props":1588,"children":1589},{"style":327},[1590],{"type":30,"value":1591}," CompareDifference",{"type":24,"tag":155,"props":1593,"children":1594},{"style":173},[1595],{"type":30,"value":551},{"type":24,"tag":155,"props":1597,"children":1598},{"style":167},[1599],{"type":30,"value":1528},{"type":24,"tag":155,"props":1601,"children":1602},{"style":327},[1603],{"type":30,"value":1604}," other",{"type":24,"tag":155,"props":1606,"children":1607},{"style":173},[1608],{"type":30,"value":560},{"type":24,"tag":155,"props":1610,"children":1611},{"class":157,"line":252},[1612],{"type":24,"tag":155,"props":1613,"children":1614},{"style":173},[1615],{"type":30,"value":694},{"type":24,"tag":875,"props":1617,"children":1619},{"id":1618},"プロパティ変更を検知してフラグを切り替える",[1620],{"type":30,"value":1618},{"type":24,"tag":32,"props":1622,"children":1623},{},[1624,1629],{"type":24,"tag":46,"props":1625,"children":1627},{"className":1626},[],[1628],{"type":30,"value":1377},{"type":30,"value":1630},"イベントをフックして、そのたびに差分状態を再計算します。",{"type":24,"tag":145,"props":1632,"children":1634},{"className":147,"code":1633,"language":149,"meta":8,"style":8},"private void Editable_PropertyChanged(object sender, PropertyChangedEventArgs e)\n{\n    // 既に差分がある状態で、差分がなくなった場合\n    if (!_initObj.CompareDifference(_editableObj) && HasDiff)\n    {\n        HasDiff = false;\n        DiffStateChanged?.Invoke(_editableObj, false);\n    }\n    // 差分がない状態で差分が発生した場合\n    else if (_initObj.CompareDifference(_editableObj) && !HasDiff)\n    {\n        HasDiff = true;\n        DiffStateChanged?.Invoke(_editableObj, true);\n    }\n}\n",[1635],{"type":24,"tag":46,"props":1636,"children":1637},{"__ignoreMap":8},[1638,1685,1692,1700,1754,1761,1781,1821,1828,1836,1895,1902,1922,1961,1968],{"type":24,"tag":155,"props":1639,"children":1640},{"class":157,"line":18},[1641,1646,1650,1654,1658,1663,1668,1672,1676,1681],{"type":24,"tag":155,"props":1642,"children":1643},{"style":237},[1644],{"type":30,"value":1645},"private",{"type":24,"tag":155,"props":1647,"children":1648},{"style":161},[1649],{"type":30,"value":356},{"type":24,"tag":155,"props":1651,"children":1652},{"style":327},[1653],{"type":30,"value":1387},{"type":24,"tag":155,"props":1655,"children":1656},{"style":173},[1657],{"type":30,"value":551},{"type":24,"tag":155,"props":1659,"children":1660},{"style":161},[1661],{"type":30,"value":1662},"object",{"type":24,"tag":155,"props":1664,"children":1665},{"style":327},[1666],{"type":30,"value":1667}," sender",{"type":24,"tag":155,"props":1669,"children":1670},{"style":173},[1671],{"type":30,"value":396},{"type":24,"tag":155,"props":1673,"children":1674},{"style":167},[1675],{"type":30,"value":664},{"type":24,"tag":155,"props":1677,"children":1678},{"style":327},[1679],{"type":30,"value":1680}," e",{"type":24,"tag":155,"props":1682,"children":1683},{"style":173},[1684],{"type":30,"value":453},{"type":24,"tag":155,"props":1686,"children":1687},{"class":157,"line":189},[1688],{"type":24,"tag":155,"props":1689,"children":1690},{"style":173},[1691],{"type":30,"value":300},{"type":24,"tag":155,"props":1693,"children":1694},{"class":157,"line":223},[1695],{"type":24,"tag":155,"props":1696,"children":1697},{"style":1411},[1698],{"type":30,"value":1699},"    // 既に差分がある状態で、差分がなくなった場合\n",{"type":24,"tag":155,"props":1701,"children":1702},{"class":157,"line":233},[1703,1708,1712,1717,1721,1725,1729,1733,1737,1741,1746,1750],{"type":24,"tag":155,"props":1704,"children":1705},{"style":161},[1706],{"type":30,"value":1707},"    if",{"type":24,"tag":155,"props":1709,"children":1710},{"style":173},[1711],{"type":30,"value":476},{"type":24,"tag":155,"props":1713,"children":1714},{"style":237},[1715],{"type":30,"value":1716},"!",{"type":24,"tag":155,"props":1718,"children":1719},{"style":479},[1720],{"type":30,"value":1432},{"type":24,"tag":155,"props":1722,"children":1723},{"style":173},[1724],{"type":30,"value":176},{"type":24,"tag":155,"props":1726,"children":1727},{"style":327},[1728],{"type":30,"value":1494},{"type":24,"tag":155,"props":1730,"children":1731},{"style":173},[1732],{"type":30,"value":551},{"type":24,"tag":155,"props":1734,"children":1735},{"style":479},[1736],{"type":30,"value":1439},{"type":24,"tag":155,"props":1738,"children":1739},{"style":173},[1740],{"type":30,"value":496},{"type":24,"tag":155,"props":1742,"children":1743},{"style":237},[1744],{"type":30,"value":1745}," &&",{"type":24,"tag":155,"props":1747,"children":1748},{"style":479},[1749],{"type":30,"value":1173},{"type":24,"tag":155,"props":1751,"children":1752},{"style":173},[1753],{"type":30,"value":453},{"type":24,"tag":155,"props":1755,"children":1756},{"class":157,"line":252},[1757],{"type":24,"tag":155,"props":1758,"children":1759},{"style":173},[1760],{"type":30,"value":462},{"type":24,"tag":155,"props":1762,"children":1763},{"class":157,"line":260},[1764,1769,1773,1777],{"type":24,"tag":155,"props":1765,"children":1766},{"style":479},[1767],{"type":30,"value":1768},"        HasDiff",{"type":24,"tag":155,"props":1770,"children":1771},{"style":173},[1772],{"type":30,"value":443},{"type":24,"tag":155,"props":1774,"children":1775},{"style":161},[1776],{"type":30,"value":1216},{"type":24,"tag":155,"props":1778,"children":1779},{"style":173},[1780],{"type":30,"value":186},{"type":24,"tag":155,"props":1782,"children":1783},{"class":157,"line":294},[1784,1789,1793,1797,1801,1805,1809,1813,1817],{"type":24,"tag":155,"props":1785,"children":1786},{"style":479},[1787],{"type":30,"value":1788},"        DiffStateChanged",{"type":24,"tag":155,"props":1790,"children":1791},{"style":237},[1792],{"type":30,"value":324},{"type":24,"tag":155,"props":1794,"children":1795},{"style":173},[1796],{"type":30,"value":176},{"type":24,"tag":155,"props":1798,"children":1799},{"style":327},[1800],{"type":30,"value":641},{"type":24,"tag":155,"props":1802,"children":1803},{"style":173},[1804],{"type":30,"value":551},{"type":24,"tag":155,"props":1806,"children":1807},{"style":479},[1808],{"type":30,"value":1439},{"type":24,"tag":155,"props":1810,"children":1811},{"style":173},[1812],{"type":30,"value":396},{"type":24,"tag":155,"props":1814,"children":1815},{"style":161},[1816],{"type":30,"value":1216},{"type":24,"tag":155,"props":1818,"children":1819},{"style":173},[1820],{"type":30,"value":560},{"type":24,"tag":155,"props":1822,"children":1823},{"class":157,"line":303},[1824],{"type":24,"tag":155,"props":1825,"children":1826},{"style":173},[1827],{"type":30,"value":569},{"type":24,"tag":155,"props":1829,"children":1830},{"class":157,"line":337},[1831],{"type":24,"tag":155,"props":1832,"children":1833},{"style":1411},[1834],{"type":30,"value":1835},"    // 差分がない状態で差分が発生した場合\n",{"type":24,"tag":155,"props":1837,"children":1838},{"class":157,"line":345},[1839,1844,1849,1853,1857,1861,1865,1869,1873,1877,1881,1886,1891],{"type":24,"tag":155,"props":1840,"children":1841},{"style":161},[1842],{"type":30,"value":1843},"    else",{"type":24,"tag":155,"props":1845,"children":1846},{"style":161},[1847],{"type":30,"value":1848}," if",{"type":24,"tag":155,"props":1850,"children":1851},{"style":173},[1852],{"type":30,"value":476},{"type":24,"tag":155,"props":1854,"children":1855},{"style":479},[1856],{"type":30,"value":1432},{"type":24,"tag":155,"props":1858,"children":1859},{"style":173},[1860],{"type":30,"value":176},{"type":24,"tag":155,"props":1862,"children":1863},{"style":327},[1864],{"type":30,"value":1494},{"type":24,"tag":155,"props":1866,"children":1867},{"style":173},[1868],{"type":30,"value":551},{"type":24,"tag":155,"props":1870,"children":1871},{"style":479},[1872],{"type":30,"value":1439},{"type":24,"tag":155,"props":1874,"children":1875},{"style":173},[1876],{"type":30,"value":496},{"type":24,"tag":155,"props":1878,"children":1879},{"style":237},[1880],{"type":30,"value":1745},{"type":24,"tag":155,"props":1882,"children":1883},{"style":237},[1884],{"type":30,"value":1885}," !",{"type":24,"tag":155,"props":1887,"children":1888},{"style":479},[1889],{"type":30,"value":1890},"HasDiff",{"type":24,"tag":155,"props":1892,"children":1893},{"style":173},[1894],{"type":30,"value":453},{"type":24,"tag":155,"props":1896,"children":1897},{"class":157,"line":456},[1898],{"type":24,"tag":155,"props":1899,"children":1900},{"style":173},[1901],{"type":30,"value":462},{"type":24,"tag":155,"props":1903,"children":1904},{"class":157,"line":465},[1905,1909,1913,1918],{"type":24,"tag":155,"props":1906,"children":1907},{"style":479},[1908],{"type":30,"value":1768},{"type":24,"tag":155,"props":1910,"children":1911},{"style":173},[1912],{"type":30,"value":443},{"type":24,"tag":155,"props":1914,"children":1915},{"style":161},[1916],{"type":30,"value":1917}," true",{"type":24,"tag":155,"props":1919,"children":1920},{"style":173},[1921],{"type":30,"value":186},{"type":24,"tag":155,"props":1923,"children":1924},{"class":157,"line":519},[1925,1929,1933,1937,1941,1945,1949,1953,1957],{"type":24,"tag":155,"props":1926,"children":1927},{"style":479},[1928],{"type":30,"value":1788},{"type":24,"tag":155,"props":1930,"children":1931},{"style":237},[1932],{"type":30,"value":324},{"type":24,"tag":155,"props":1934,"children":1935},{"style":173},[1936],{"type":30,"value":176},{"type":24,"tag":155,"props":1938,"children":1939},{"style":327},[1940],{"type":30,"value":641},{"type":24,"tag":155,"props":1942,"children":1943},{"style":173},[1944],{"type":30,"value":551},{"type":24,"tag":155,"props":1946,"children":1947},{"style":479},[1948],{"type":30,"value":1439},{"type":24,"tag":155,"props":1950,"children":1951},{"style":173},[1952],{"type":30,"value":396},{"type":24,"tag":155,"props":1954,"children":1955},{"style":161},[1956],{"type":30,"value":1917},{"type":24,"tag":155,"props":1958,"children":1959},{"style":173},[1960],{"type":30,"value":560},{"type":24,"tag":155,"props":1962,"children":1963},{"class":157,"line":540},[1964],{"type":24,"tag":155,"props":1965,"children":1966},{"style":173},[1967],{"type":30,"value":569},{"type":24,"tag":155,"props":1969,"children":1970},{"class":157,"line":563},[1971],{"type":24,"tag":155,"props":1972,"children":1973},{"style":173},[1974],{"type":30,"value":694},{"type":24,"tag":32,"props":1976,"children":1977},{},[1978,1980,1985,1987,1992],{"type":30,"value":1979},"ここで工夫している点は、",{"type":24,"tag":38,"props":1981,"children":1982},{},[1983],{"type":30,"value":1984},"「フラグが変化した瞬間だけ」イベントを発火",{"type":30,"value":1986},"している点です。毎回",{"type":24,"tag":46,"props":1988,"children":1990},{"className":1989},[],[1991],{"type":30,"value":1377},{"type":30,"value":1993},"のたびにイベントを流してしまうと、Blazorのレンダリングループに過剰な負荷がかかります。PICOMの楽譜エディタはピアノロールをドラッグするだけで毎秒数十回のプロパティ変更が走るので、イベントが暴れ出さないように「差分あり↔差分なし」の遷移時だけに絞っています。",{"type":24,"tag":25,"props":1995,"children":1997},{"id":1996},"実際の使われ方保存ボタンの活性判定",[1998],{"type":30,"value":1999},"実際の使われ方：保存ボタンの活性判定",{"type":24,"tag":32,"props":2001,"children":2002},{},[2003],{"type":30,"value":2004},"ViewModel側では次のような感じで使います。",{"type":24,"tag":145,"props":2006,"children":2008},{"className":147,"code":2007,"language":149,"meta":8,"style":8},"var initial = LoadFromStorage();          // 初期データ\nvar editable = initial.Clone();           // 編集可能なコピー\n_diff = new DiffDetectableObject\u003CMusic>(initial, editable);\n\n_diff.DiffStateChanged += (music, hasDiff) =>\n{\n    CanSave = hasDiff;\n    TitleBarMark = hasDiff ? \"*\" : \"\";\n    StateHasChanged();\n};\n",[2009],{"type":24,"tag":46,"props":2010,"children":2011},{"__ignoreMap":8},[2012,2044,2081,2131,2138,2184,2191,2211,2259,2271],{"type":24,"tag":155,"props":2013,"children":2014},{"class":157,"line":18},[2015,2020,2025,2029,2034,2039],{"type":24,"tag":155,"props":2016,"children":2017},{"style":237},[2018],{"type":30,"value":2019},"var",{"type":24,"tag":155,"props":2021,"children":2022},{"style":327},[2023],{"type":30,"value":2024}," initial",{"type":24,"tag":155,"props":2026,"children":2027},{"style":173},[2028],{"type":30,"value":443},{"type":24,"tag":155,"props":2030,"children":2031},{"style":327},[2032],{"type":30,"value":2033}," LoadFromStorage",{"type":24,"tag":155,"props":2035,"children":2036},{"style":173},[2037],{"type":30,"value":2038},"();",{"type":24,"tag":155,"props":2040,"children":2041},{"style":1411},[2042],{"type":30,"value":2043},"          // 初期データ\n",{"type":24,"tag":155,"props":2045,"children":2046},{"class":157,"line":189},[2047,2051,2055,2059,2063,2067,2072,2076],{"type":24,"tag":155,"props":2048,"children":2049},{"style":237},[2050],{"type":30,"value":2019},{"type":24,"tag":155,"props":2052,"children":2053},{"style":327},[2054],{"type":30,"value":1108},{"type":24,"tag":155,"props":2056,"children":2057},{"style":173},[2058],{"type":30,"value":443},{"type":24,"tag":155,"props":2060,"children":2061},{"style":479},[2062],{"type":30,"value":2024},{"type":24,"tag":155,"props":2064,"children":2065},{"style":173},[2066],{"type":30,"value":176},{"type":24,"tag":155,"props":2068,"children":2069},{"style":327},[2070],{"type":30,"value":2071},"Clone",{"type":24,"tag":155,"props":2073,"children":2074},{"style":173},[2075],{"type":30,"value":2038},{"type":24,"tag":155,"props":2077,"children":2078},{"style":1411},[2079],{"type":30,"value":2080},"           // 編集可能なコピー\n",{"type":24,"tag":155,"props":2082,"children":2083},{"class":157,"line":223},[2084,2089,2093,2097,2101,2105,2110,2114,2119,2123,2127],{"type":24,"tag":155,"props":2085,"children":2086},{"style":479},[2087],{"type":30,"value":2088},"_diff",{"type":24,"tag":155,"props":2090,"children":2091},{"style":173},[2092],{"type":30,"value":443},{"type":24,"tag":155,"props":2094,"children":2095},{"style":237},[2096],{"type":30,"value":506},{"type":24,"tag":155,"props":2098,"children":2099},{"style":167},[2100],{"type":30,"value":1015},{"type":24,"tag":155,"props":2102,"children":2103},{"style":173},[2104],{"type":30,"value":366},{"type":24,"tag":155,"props":2106,"children":2107},{"style":167},[2108],{"type":30,"value":2109},"Music",{"type":24,"tag":155,"props":2111,"children":2112},{"style":173},[2113],{"type":30,"value":376},{"type":24,"tag":155,"props":2115,"children":2116},{"style":479},[2117],{"type":30,"value":2118},"initial",{"type":24,"tag":155,"props":2120,"children":2121},{"style":173},[2122],{"type":30,"value":396},{"type":24,"tag":155,"props":2124,"children":2125},{"style":479},[2126],{"type":30,"value":1108},{"type":24,"tag":155,"props":2128,"children":2129},{"style":173},[2130],{"type":30,"value":560},{"type":24,"tag":155,"props":2132,"children":2133},{"class":157,"line":233},[2134],{"type":24,"tag":155,"props":2135,"children":2136},{"emptyLinePlaceholder":227},[2137],{"type":30,"value":230},{"type":24,"tag":155,"props":2139,"children":2140},{"class":157,"line":252},[2141,2145,2149,2154,2158,2162,2167,2171,2175,2179],{"type":24,"tag":155,"props":2142,"children":2143},{"style":479},[2144],{"type":30,"value":2088},{"type":24,"tag":155,"props":2146,"children":2147},{"style":173},[2148],{"type":30,"value":176},{"type":24,"tag":155,"props":2150,"children":2151},{"style":479},[2152],{"type":30,"value":2153},"DiffStateChanged",{"type":24,"tag":155,"props":2155,"children":2156},{"style":237},[2157],{"type":30,"value":1382},{"type":24,"tag":155,"props":2159,"children":2160},{"style":173},[2161],{"type":30,"value":476},{"type":24,"tag":155,"props":2163,"children":2164},{"style":327},[2165],{"type":30,"value":2166},"music",{"type":24,"tag":155,"props":2168,"children":2169},{"style":173},[2170],{"type":30,"value":396},{"type":24,"tag":155,"props":2172,"children":2173},{"style":327},[2174],{"type":30,"value":1122},{"type":24,"tag":155,"props":2176,"children":2177},{"style":173},[2178],{"type":30,"value":496},{"type":24,"tag":155,"props":2180,"children":2181},{"style":237},[2182],{"type":30,"value":2183}," =>\n",{"type":24,"tag":155,"props":2185,"children":2186},{"class":157,"line":260},[2187],{"type":24,"tag":155,"props":2188,"children":2189},{"style":173},[2190],{"type":30,"value":300},{"type":24,"tag":155,"props":2192,"children":2193},{"class":157,"line":294},[2194,2199,2203,2207],{"type":24,"tag":155,"props":2195,"children":2196},{"style":479},[2197],{"type":30,"value":2198},"    CanSave",{"type":24,"tag":155,"props":2200,"children":2201},{"style":173},[2202],{"type":30,"value":443},{"type":24,"tag":155,"props":2204,"children":2205},{"style":479},[2206],{"type":30,"value":1122},{"type":24,"tag":155,"props":2208,"children":2209},{"style":173},[2210],{"type":30,"value":186},{"type":24,"tag":155,"props":2212,"children":2213},{"class":157,"line":303},[2214,2219,2223,2227,2232,2237,2242,2247,2251,2255],{"type":24,"tag":155,"props":2215,"children":2216},{"style":479},[2217],{"type":30,"value":2218},"    TitleBarMark",{"type":24,"tag":155,"props":2220,"children":2221},{"style":173},[2222],{"type":30,"value":443},{"type":24,"tag":155,"props":2224,"children":2225},{"style":479},[2226],{"type":30,"value":1122},{"type":24,"tag":155,"props":2228,"children":2229},{"style":237},[2230],{"type":30,"value":2231}," ?",{"type":24,"tag":155,"props":2233,"children":2234},{"style":768},[2235],{"type":30,"value":2236}," \"",{"type":24,"tag":155,"props":2238,"children":2240},{"style":2239},"--shiki-default:#C98A7D",[2241],{"type":30,"value":51},{"type":24,"tag":155,"props":2243,"children":2244},{"style":768},[2245],{"type":30,"value":2246},"\"",{"type":24,"tag":155,"props":2248,"children":2249},{"style":237},[2250],{"type":30,"value":286},{"type":24,"tag":155,"props":2252,"children":2253},{"style":768},[2254],{"type":30,"value":771},{"type":24,"tag":155,"props":2256,"children":2257},{"style":173},[2258],{"type":30,"value":186},{"type":24,"tag":155,"props":2260,"children":2261},{"class":157,"line":337},[2262,2267],{"type":24,"tag":155,"props":2263,"children":2264},{"style":327},[2265],{"type":30,"value":2266},"    StateHasChanged",{"type":24,"tag":155,"props":2268,"children":2269},{"style":173},[2270],{"type":30,"value":516},{"type":24,"tag":155,"props":2272,"children":2273},{"class":157,"line":345},[2274],{"type":24,"tag":155,"props":2275,"children":2276},{"style":173},[2277],{"type":30,"value":2278},"};\n",{"type":24,"tag":32,"props":2280,"children":2281},{},[2282,2284,2290,2292,2297,2299,2305,2307,2313],{"type":30,"value":2283},"UI側は",{"type":24,"tag":46,"props":2285,"children":2287},{"className":2286},[],[2288],{"type":30,"value":2289},"CanSave",{"type":30,"value":2291},"プロパティをそのままボタンの活性（enabled）にバインドすれば終わりです。ユーザーが何か変更したら",{"type":24,"tag":46,"props":2293,"children":2295},{"className":2294},[],[2296],{"type":30,"value":1890},{"type":30,"value":2298},"が",{"type":24,"tag":46,"props":2300,"children":2302},{"className":2301},[],[2303],{"type":30,"value":2304},"true",{"type":30,"value":2306},"になり、保存後に",{"type":24,"tag":46,"props":2308,"children":2310},{"className":2309},[],[2311],{"type":30,"value":2312},"EditableToInit",{"type":30,"value":2314},"を呼んで初期状態を「今」に巻き戻します。",{"type":24,"tag":145,"props":2316,"children":2318},{"className":147,"code":2317,"language":149,"meta":8,"style":8},"public void EditableToInit(T newEditable)\n{\n    _initObj = _editableObj;\n    Value = newEditable;\n    HasDiff = false;\n    DiffStateChanged?.Invoke(_editableObj, false);\n}\n",[2319],{"type":24,"tag":46,"props":2320,"children":2321},{"__ignoreMap":8},[2322,2355,2362,2382,2402,2422,2462],{"type":24,"tag":155,"props":2323,"children":2324},{"class":157,"line":18},[2325,2329,2333,2338,2342,2346,2351],{"type":24,"tag":155,"props":2326,"children":2327},{"style":237},[2328],{"type":30,"value":266},{"type":24,"tag":155,"props":2330,"children":2331},{"style":161},[2332],{"type":30,"value":356},{"type":24,"tag":155,"props":2334,"children":2335},{"style":327},[2336],{"type":30,"value":2337}," EditableToInit",{"type":24,"tag":155,"props":2339,"children":2340},{"style":173},[2341],{"type":30,"value":551},{"type":24,"tag":155,"props":2343,"children":2344},{"style":167},[2345],{"type":30,"value":371},{"type":24,"tag":155,"props":2347,"children":2348},{"style":327},[2349],{"type":30,"value":2350}," newEditable",{"type":24,"tag":155,"props":2352,"children":2353},{"style":173},[2354],{"type":30,"value":453},{"type":24,"tag":155,"props":2356,"children":2357},{"class":157,"line":189},[2358],{"type":24,"tag":155,"props":2359,"children":2360},{"style":173},[2361],{"type":30,"value":300},{"type":24,"tag":155,"props":2363,"children":2364},{"class":157,"line":223},[2365,2370,2374,2378],{"type":24,"tag":155,"props":2366,"children":2367},{"style":479},[2368],{"type":30,"value":2369},"    _initObj",{"type":24,"tag":155,"props":2371,"children":2372},{"style":173},[2373],{"type":30,"value":443},{"type":24,"tag":155,"props":2375,"children":2376},{"style":479},[2377],{"type":30,"value":1263},{"type":24,"tag":155,"props":2379,"children":2380},{"style":173},[2381],{"type":30,"value":186},{"type":24,"tag":155,"props":2383,"children":2384},{"class":157,"line":233},[2385,2390,2394,2398],{"type":24,"tag":155,"props":2386,"children":2387},{"style":479},[2388],{"type":30,"value":2389},"    Value",{"type":24,"tag":155,"props":2391,"children":2392},{"style":173},[2393],{"type":30,"value":443},{"type":24,"tag":155,"props":2395,"children":2396},{"style":479},[2397],{"type":30,"value":2350},{"type":24,"tag":155,"props":2399,"children":2400},{"style":173},[2401],{"type":30,"value":186},{"type":24,"tag":155,"props":2403,"children":2404},{"class":157,"line":252},[2405,2410,2414,2418],{"type":24,"tag":155,"props":2406,"children":2407},{"style":479},[2408],{"type":30,"value":2409},"    HasDiff",{"type":24,"tag":155,"props":2411,"children":2412},{"style":173},[2413],{"type":30,"value":443},{"type":24,"tag":155,"props":2415,"children":2416},{"style":161},[2417],{"type":30,"value":1216},{"type":24,"tag":155,"props":2419,"children":2420},{"style":173},[2421],{"type":30,"value":186},{"type":24,"tag":155,"props":2423,"children":2424},{"class":157,"line":260},[2425,2430,2434,2438,2442,2446,2450,2454,2458],{"type":24,"tag":155,"props":2426,"children":2427},{"style":479},[2428],{"type":30,"value":2429},"    DiffStateChanged",{"type":24,"tag":155,"props":2431,"children":2432},{"style":237},[2433],{"type":30,"value":324},{"type":24,"tag":155,"props":2435,"children":2436},{"style":173},[2437],{"type":30,"value":176},{"type":24,"tag":155,"props":2439,"children":2440},{"style":327},[2441],{"type":30,"value":641},{"type":24,"tag":155,"props":2443,"children":2444},{"style":173},[2445],{"type":30,"value":551},{"type":24,"tag":155,"props":2447,"children":2448},{"style":479},[2449],{"type":30,"value":1439},{"type":24,"tag":155,"props":2451,"children":2452},{"style":173},[2453],{"type":30,"value":396},{"type":24,"tag":155,"props":2455,"children":2456},{"style":161},[2457],{"type":30,"value":1216},{"type":24,"tag":155,"props":2459,"children":2460},{"style":173},[2461],{"type":30,"value":560},{"type":24,"tag":155,"props":2463,"children":2464},{"class":157,"line":294},[2465],{"type":24,"tag":155,"props":2466,"children":2467},{"style":173},[2468],{"type":30,"value":694},{"type":24,"tag":25,"props":2470,"children":2472},{"id":2471},"注意点propertychanged解除忘れでメモリリーク",[2473],{"type":30,"value":2474},"注意点：PropertyChanged解除忘れでメモリリーク",{"type":24,"tag":32,"props":2476,"children":2477},{},[2478,2480,2485],{"type":30,"value":2479},"プロパティに新しいオブジェクトをセットしたときの",{"type":24,"tag":38,"props":2481,"children":2482},{},[2483],{"type":30,"value":2484},"イベント解除",{"type":30,"value":2486},"を忘れると、イベント購読がどんどん溜まる現象が発生します。",{"type":24,"tag":145,"props":2488,"children":2490},{"className":147,"code":2489,"language":149,"meta":8,"style":8},"public T Value\n{\n    get => _editableObj;\n    set\n    {\n        _editableObj.PropertyChanged -= Editable_PropertyChanged;  // ← 忘れがち\n        _editableObj = value;\n        _editableObj.PropertyChanged += Editable_PropertyChanged;\n    }\n}\n",[2491],{"type":24,"tag":46,"props":2492,"children":2493},{"__ignoreMap":8},[2494,2510,2517,2537,2545,2552,2585,2604,2631,2638],{"type":24,"tag":155,"props":2495,"children":2496},{"class":157,"line":18},[2497,2501,2505],{"type":24,"tag":155,"props":2498,"children":2499},{"style":237},[2500],{"type":30,"value":266},{"type":24,"tag":155,"props":2502,"children":2503},{"style":479},[2504],{"type":30,"value":386},{"type":24,"tag":155,"props":2506,"children":2507},{"style":479},[2508],{"type":30,"value":2509}," Value\n",{"type":24,"tag":155,"props":2511,"children":2512},{"class":157,"line":189},[2513],{"type":24,"tag":155,"props":2514,"children":2515},{"style":173},[2516],{"type":30,"value":300},{"type":24,"tag":155,"props":2518,"children":2519},{"class":157,"line":223},[2520,2525,2529,2533],{"type":24,"tag":155,"props":2521,"children":2522},{"style":327},[2523],{"type":30,"value":2524},"    get",{"type":24,"tag":155,"props":2526,"children":2527},{"style":237},[2528],{"type":30,"value":811},{"type":24,"tag":155,"props":2530,"children":2531},{"style":479},[2532],{"type":30,"value":1263},{"type":24,"tag":155,"props":2534,"children":2535},{"style":173},[2536],{"type":30,"value":186},{"type":24,"tag":155,"props":2538,"children":2539},{"class":157,"line":233},[2540],{"type":24,"tag":155,"props":2541,"children":2542},{"style":479},[2543],{"type":30,"value":2544},"    set\n",{"type":24,"tag":155,"props":2546,"children":2547},{"class":157,"line":252},[2548],{"type":24,"tag":155,"props":2549,"children":2550},{"style":173},[2551],{"type":30,"value":462},{"type":24,"tag":155,"props":2553,"children":2554},{"class":157,"line":260},[2555,2559,2563,2567,2572,2576,2580],{"type":24,"tag":155,"props":2556,"children":2557},{"style":479},[2558],{"type":30,"value":1349},{"type":24,"tag":155,"props":2560,"children":2561},{"style":173},[2562],{"type":30,"value":176},{"type":24,"tag":155,"props":2564,"children":2565},{"style":479},[2566],{"type":30,"value":1377},{"type":24,"tag":155,"props":2568,"children":2569},{"style":237},[2570],{"type":30,"value":2571}," -=",{"type":24,"tag":155,"props":2573,"children":2574},{"style":479},[2575],{"type":30,"value":1387},{"type":24,"tag":155,"props":2577,"children":2578},{"style":173},[2579],{"type":30,"value":1188},{"type":24,"tag":155,"props":2581,"children":2582},{"style":1411},[2583],{"type":30,"value":2584},"  // ← 忘れがち\n",{"type":24,"tag":155,"props":2586,"children":2587},{"class":157,"line":294},[2588,2592,2596,2600],{"type":24,"tag":155,"props":2589,"children":2590},{"style":479},[2591],{"type":30,"value":1349},{"type":24,"tag":155,"props":2593,"children":2594},{"style":173},[2595],{"type":30,"value":443},{"type":24,"tag":155,"props":2597,"children":2598},{"style":479},[2599],{"type":30,"value":405},{"type":24,"tag":155,"props":2601,"children":2602},{"style":173},[2603],{"type":30,"value":186},{"type":24,"tag":155,"props":2605,"children":2606},{"class":157,"line":303},[2607,2611,2615,2619,2623,2627],{"type":24,"tag":155,"props":2608,"children":2609},{"style":479},[2610],{"type":30,"value":1349},{"type":24,"tag":155,"props":2612,"children":2613},{"style":173},[2614],{"type":30,"value":176},{"type":24,"tag":155,"props":2616,"children":2617},{"style":479},[2618],{"type":30,"value":1377},{"type":24,"tag":155,"props":2620,"children":2621},{"style":237},[2622],{"type":30,"value":1382},{"type":24,"tag":155,"props":2624,"children":2625},{"style":479},[2626],{"type":30,"value":1387},{"type":24,"tag":155,"props":2628,"children":2629},{"style":173},[2630],{"type":30,"value":186},{"type":24,"tag":155,"props":2632,"children":2633},{"class":157,"line":337},[2634],{"type":24,"tag":155,"props":2635,"children":2636},{"style":173},[2637],{"type":30,"value":569},{"type":24,"tag":155,"props":2639,"children":2640},{"class":157,"line":345},[2641],{"type":24,"tag":155,"props":2642,"children":2643},{"style":173},[2644],{"type":30,"value":694},{"type":24,"tag":32,"props":2646,"children":2647},{},[2648,2653],{"type":24,"tag":46,"props":2649,"children":2651},{"className":2650},[],[2652],{"type":30,"value":1377},{"type":30,"value":2654},"はC#のイベントの中でも特にリークしやすいパターンで、「古いインスタンスを参照しっぱなし」の状態を作りやすいです。Blazor WASMはアプリ全体で1つのプロセスなので、ここでリークするとセッション中にずっと残ります。",{"type":24,"tag":2656,"props":2657,"children":2658},"caution-box",{},[2659],{"type":24,"tag":32,"props":2660,"children":2661},{},[2662,2668,2670,2675],{"type":24,"tag":46,"props":2663,"children":2665},{"className":2664},[],[2666],{"type":30,"value":2667},"IDisposable",{"type":30,"value":2669},"の実装も本来は検討すべきですが、PICOMではViewModelの寿命がアプリ起動時〜終了時と長いため、デタッチはすべてセッター内で完結させて簡潔にしました。よりシビアな環境（コンポーネントが頻繁に作り直されるケース）では、",{"type":24,"tag":46,"props":2671,"children":2673},{"className":2672},[],[2674],{"type":30,"value":2667},{"type":30,"value":2676},"を明示的に実装した方が安全です。",{"type":24,"tag":25,"props":2678,"children":2680},{"id":2679},"まとめ",[2681],{"type":30,"value":2679},{"type":24,"tag":917,"props":2683,"children":2684},{},[2685,2695,2705,2710],{"type":24,"tag":921,"props":2686,"children":2687},{},[2688,2693],{"type":24,"tag":46,"props":2689,"children":2691},{"className":2690},[],[2692],{"type":30,"value":71},{"type":30,"value":2694},"は20行程度で書けるので、小規模アプリならMVVM Toolkitを避けて自前で書くのも選択肢",{"type":24,"tag":921,"props":2696,"children":2697},{},[2698,2703],{"type":24,"tag":46,"props":2699,"children":2701},{"className":2700},[],[2702],{"type":30,"value":79},{"type":30,"value":2704},"で初期スナップショットと編集中オブジェクトの差分をイベント通知に変換できる",{"type":24,"tag":921,"props":2706,"children":2707},{},[2708],{"type":30,"value":2709},"イベントは「差分フラグが変化した瞬間だけ」に絞ると、レンダリングループに優しい",{"type":24,"tag":921,"props":2711,"children":2712},{},[2713,2718],{"type":24,"tag":46,"props":2714,"children":2716},{"className":2715},[],[2717],{"type":30,"value":1377},{"type":30,"value":2719},"の購読解除は徹底する。忘れるとメモリリークの温床になる",{"type":24,"tag":2721,"props":2722,"children":2723},"style",{},[2724],{"type":30,"value":2725},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":8,"searchDepth":189,"depth":189,"links":2727},[2728,2729,2732,2735,2736,2737],{"id":27,"depth":189,"text":27},{"id":121,"depth":189,"text":124,"children":2730},[2731],{"id":877,"depth":223,"text":880},{"id":970,"depth":189,"text":973,"children":2733},[2734],{"id":1618,"depth":223,"text":1618},{"id":1996,"depth":189,"text":1999},{"id":2471,"depth":189,"text":2474},{"id":2679,"depth":189,"text":2679},"markdown","content:articles:tech:blazor:diff-detectable-object.md","content","articles/tech/blazor/diff-detectable-object.md","articles/tech/blazor/diff-detectable-object","md",{"_path":2745,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":2746,"description":2747,"date":2748,"tags":2749,"rowTypeId":18,"sitemap":2753,"body":2754,"_type":2738,"_id":5667,"_source":2740,"_file":5668,"_stem":5669,"_extension":2743},"/articles/tech/blazor/messagepack-pim-format","MessagePackで独自バイナリファイル形式（PIM）をバージョン管理対応で設計する","音楽作成アプリPICOMで独自のバイナリファイル形式「PIM」をMessagePackで実装しました。なぜJSONでなくMessagePackを選んだのか、`[Key]`番号の運用ルール、将来のフォーマット進化に備えるVersionフィールド戦略について解説します。","2026-04-15",[13,2750,2751,2752,17],"MessagePack","ファイルフォーマット","シリアライズ",{"loc":2745,"lastmod":2748,"priority":18},{"type":21,"children":2755,"toc":5652},[2756,2760,2780,2792,2816,2822,2835,2847,2852,2887,2893,2898,2910,2937,2996,3002,3028,3419,3438,3497,3508,3513,3545,3557,4086,4098,4104,4139,4569,4581,4620,4632,4661,4667,4688,4775,4795,4808,4813,4819,4832,5079,5105,5111,5124,5158,5554,5590,5609,5613,5648],{"type":24,"tag":25,"props":2757,"children":2758},{"id":27},[2759],{"type":30,"value":27},{"type":24,"tag":32,"props":2761,"children":2762},{},[2763,2765,2770,2772,2778],{"type":30,"value":2764},"PICOMというチップチューン向け音楽作成Webアプリを開発する中で、「楽譜データをどのフォーマットで保存するか」を決める必要がありました。最終的に選んだのが",{"type":24,"tag":83,"props":2766,"children":2768},{"content":2767},"バイナリ形式のシリアライズフォーマット。JSONより小さくて速い",[2769],{"type":30,"value":2750},{"type":30,"value":2771},"で、これに",{"type":24,"tag":46,"props":2773,"children":2775},{"className":2774},[],[2776],{"type":30,"value":2777},".pim",{"type":30,"value":2779},"という独自拡張子を付けて「PIMフォーマット」と呼んでいます。",{"type":24,"tag":32,"props":2781,"children":2782},{},[2783,2785,2790],{"type":30,"value":2784},"この記事では、JSONではなくMessagePackを選んだ理由と、将来のフォーマット変更に備えた",{"type":24,"tag":38,"props":2786,"children":2787},{},[2788],{"type":30,"value":2789},"バージョン管理の設計",{"type":30,"value":2791},"、そして実装で気をつけた点を書きます。「自作アプリのセーブデータをどう設計するか」で悩んでいる方の参考になれば嬉しいです。",{"type":24,"tag":92,"props":2793,"children":2794},{},[2795],{"type":24,"tag":32,"props":2796,"children":2797},{},[2798,2800,2806,2808,2814],{"type":30,"value":2799},"PICOMのセーブデータ形式「PIM」はMessagePackベースで、バイナリの軽量さとフォーマット拡張への耐性を両立させています。モデルクラスに",{"type":24,"tag":46,"props":2801,"children":2803},{"className":2802},[],[2804],{"type":30,"value":2805},"Version",{"type":30,"value":2807},"プロパティを埋め込むことで将来のフォーマット変更に備え、",{"type":24,"tag":46,"props":2809,"children":2811},{"className":2810},[],[2812],{"type":30,"value":2813},"[Key]",{"type":30,"value":2815},"番号の管理ルールやReadResult enumでデシリアライズ失敗を型で表現する設計も紹介します。",{"type":24,"tag":25,"props":2817,"children":2819},{"id":2818},"なぜjsonではなくmessagepackを選んだのか",[2820],{"type":30,"value":2821},"なぜJSONではなくMessagePackを選んだのか",{"type":24,"tag":32,"props":2823,"children":2824},{},[2825,2827,2833],{"type":30,"value":2826},"一番最初はJSONで実装するつもりでした。デバッグしやすいですし、",{"type":24,"tag":46,"props":2828,"children":2830},{"className":2829},[],[2831],{"type":30,"value":2832},"System.Text.Json",{"type":30,"value":2834},"は.NET標準で追加の依存がいりません。ただ、PICOMで扱うデータの性質を考えた結果、MessagePackに切り替えました。",{"type":24,"tag":32,"props":2836,"children":2837},{},[2838,2840,2845],{"type":30,"value":2839},"決め手を一言で言うと、",{"type":24,"tag":38,"props":2841,"children":2842},{},[2843],{"type":30,"value":2844},"バイナリフォーマットの利点が欲しかったから",{"type":30,"value":2846},"です。",{"type":24,"tag":875,"props":2848,"children":2850},{"id":2849},"サイズが小さい",[2851],{"type":30,"value":2849},{"type":24,"tag":32,"props":2853,"children":2854},{},[2855,2857,2863,2865,2871,2872,2878,2880,2885],{"type":30,"value":2856},"PICOMの1曲は数十〜数百の音符と、各音符にエフェクト情報が付きます。JSONで書くとキー名（",{"type":24,"tag":46,"props":2858,"children":2860},{"className":2859},[],[2861],{"type":30,"value":2862},"\"scale\"",{"type":30,"value":2864},", ",{"type":24,"tag":46,"props":2866,"children":2868},{"className":2867},[],[2869],{"type":30,"value":2870},"\"scaleNumber\"",{"type":30,"value":2864},{"type":24,"tag":46,"props":2873,"children":2875},{"className":2874},[],[2876],{"type":30,"value":2877},"\"length\"",{"type":30,"value":2879},"...）が毎音符ごとに繰り返され、中身より外側が重くなりがちです。MessagePackはキーを整数に置き換えられるので、同じデータで比較すると",{"type":24,"tag":38,"props":2881,"children":2882},{},[2883],{"type":30,"value":2884},"ざっくり半分以下",{"type":30,"value":2886},"になります。ブラウザのIndexedDBに保存する以上、軽いに越したことはありません。",{"type":24,"tag":875,"props":2888,"children":2890},{"id":2889},"indexeddbとの相性が良い",[2891],{"type":30,"value":2892},"IndexedDBとの相性が良い",{"type":24,"tag":32,"props":2894,"children":2895},{},[2896],{"type":30,"value":2897},"最終的にIndexedDBに入れるのはバイト列なので、中間でJSON文字列を経由するよりも、最初からバイナリで扱える方がシンプルになります。余計な文字列⇔バイト列の変換が挟まらない分、保存・読み込み周りのコードの見通しも良くなります。",{"type":24,"tag":32,"props":2899,"children":2900},{},[2901,2903,2908],{"type":30,"value":2902},"ただし、バイナリなら何でも良かったわけではありません。独自のバイナリ形式を自力で設計すると、",{"type":24,"tag":38,"props":2904,"children":2905},{},[2906],{"type":30,"value":2907},"フィールドを1つ追加しただけで既存のセーブデータが一切読めなくなる",{"type":30,"value":2909}," という事態に容易に陥ります。PICOMの楽譜データには、今後も新しいフィールドを追加していく可能性が残っており、拡張に耐えるフォーマットであることは必須条件でした。",{"type":24,"tag":32,"props":2911,"children":2912},{},[2913,2915,2921,2923,2928,2930,2935],{"type":30,"value":2914},"MessagePackを選んだのは、この問題への答えを持っていたからです。",{"type":24,"tag":46,"props":2916,"children":2918},{"className":2917},[],[2919],{"type":30,"value":2920},"[Key(n)]",{"type":30,"value":2922}," の番号運用ルールさえ守れば、",{"type":24,"tag":38,"props":2924,"children":2925},{},[2926],{"type":30,"value":2927},"新しいフィールドを追加しても古いデータが壊れず、逆に古いコードで新しいデータを読んでも余分なフィールドが無視されるだけで済みます",{"type":30,"value":2929},"。どう拡張に耐えるかは後述の「Versionフィールドで将来のフォーマット変更に備える」で具体的に扱いますが、",{"type":24,"tag":38,"props":2931,"children":2932},{},[2933],{"type":30,"value":2934},"「バイナリの扱いやすさ」と「フォーマット拡張への耐性」が両立している",{"type":30,"value":2936}," 点こそが、PICOMが最終的にMessagePackを選んだ決定打でした。",{"type":24,"tag":907,"props":2938,"children":2939},{"cons-label":909,"pros-label":910},[2940,2966],{"type":24,"tag":913,"props":2941,"children":2942},{"v-slot:pros":8},[2943],{"type":24,"tag":917,"props":2944,"children":2945},{},[2946,2951,2956],{"type":24,"tag":921,"props":2947,"children":2948},{},[2949],{"type":30,"value":2950},"JSONより小さい（同じデータで半分程度）",{"type":24,"tag":921,"props":2952,"children":2953},{},[2954],{"type":30,"value":2955},"バイナリなのでIndexedDBへの保存時に余計な文字列変換が要らない",{"type":24,"tag":921,"props":2957,"children":2958},{},[2959,2964],{"type":24,"tag":46,"props":2960,"children":2962},{"className":2961},[],[2963],{"type":30,"value":2813},{"type":30,"value":2965},"番号を守ればフィールド追加しても古いデータが壊れない拡張性",{"type":24,"tag":913,"props":2967,"children":2968},{"v-slot:cons":8},[2969,2974,2986],{"type":24,"tag":921,"props":2970,"children":2971},{},[2972],{"type":30,"value":2973},"人間が目視できない（デバッグ時にbase64→MessagePack CLI必須）",{"type":24,"tag":921,"props":2975,"children":2976},{},[2977,2979,2984],{"type":30,"value":2978},"NuGetパッケージ",{"type":24,"tag":46,"props":2980,"children":2982},{"className":2981},[],[2983],{"type":30,"value":2750},{"type":30,"value":2985},"への依存が増える",{"type":24,"tag":921,"props":2987,"children":2988},{},[2989,2994],{"type":24,"tag":46,"props":2990,"children":2992},{"className":2991},[],[2993],{"type":30,"value":2813},{"type":30,"value":2995},"番号を後から変更できない",{"type":24,"tag":25,"props":2997,"children":2999},{"id":2998},"pimフォーマットのデータモデル",[3000],{"type":30,"value":3001},"PIMフォーマットのデータモデル",{"type":24,"tag":32,"props":3003,"children":3004},{},[3005,3007,3013,3014,3020,3021,3026],{"type":30,"value":3006},"セーブデータのトップレベルは次の",{"type":24,"tag":46,"props":3008,"children":3010},{"className":3009},[],[3011],{"type":30,"value":3012},"MusicDataModel",{"type":30,"value":2846},{"type":24,"tag":46,"props":3015,"children":3017},{"className":3016},[],[3018],{"type":30,"value":3019},"[MessagePackObject]",{"type":30,"value":73},{"type":24,"tag":46,"props":3022,"children":3024},{"className":3023},[],[3025],{"type":30,"value":2920},{"type":30,"value":3027},"の組み合わせでフィールド位置を明示しています。",{"type":24,"tag":145,"props":3029,"children":3031},{"className":147,"code":3030,"language":149,"meta":8,"style":8},"[MessagePackObject]\npublic class MusicDataModel\n{\n    [Key(0)]\n    public int Version { get; set; } = 2;\n\n    [Key(1)]\n    public MusicMetadataModel Metadata { get; set; } = new();\n\n    [Key(2)]\n    public MusicSettingsModel Settings { get; set; } = new();\n\n    [Key(3)]\n    public TrackDataModel[] Tracks { get; set; } = [];\n}\n",[3032],{"type":24,"tag":46,"props":3033,"children":3034},{"__ignoreMap":8},[3035,3053,3069,3076,3104,3158,3165,3189,3242,3249,3273,3326,3333,3357,3412],{"type":24,"tag":155,"props":3036,"children":3037},{"class":157,"line":18},[3038,3043,3048],{"type":24,"tag":155,"props":3039,"children":3040},{"style":173},[3041],{"type":30,"value":3042},"[",{"type":24,"tag":155,"props":3044,"children":3045},{"style":167},[3046],{"type":30,"value":3047},"MessagePackObject",{"type":24,"tag":155,"props":3049,"children":3050},{"style":173},[3051],{"type":30,"value":3052},"]\n",{"type":24,"tag":155,"props":3054,"children":3055},{"class":157,"line":189},[3056,3060,3064],{"type":24,"tag":155,"props":3057,"children":3058},{"style":237},[3059],{"type":30,"value":266},{"type":24,"tag":155,"props":3061,"children":3062},{"style":237},[3063],{"type":30,"value":276},{"type":24,"tag":155,"props":3065,"children":3066},{"style":167},[3067],{"type":30,"value":3068}," MusicDataModel\n",{"type":24,"tag":155,"props":3070,"children":3071},{"class":157,"line":223},[3072],{"type":24,"tag":155,"props":3073,"children":3074},{"style":173},[3075],{"type":30,"value":300},{"type":24,"tag":155,"props":3077,"children":3078},{"class":157,"line":233},[3079,3084,3089,3093,3099],{"type":24,"tag":155,"props":3080,"children":3081},{"style":173},[3082],{"type":30,"value":3083},"    [",{"type":24,"tag":155,"props":3085,"children":3086},{"style":167},[3087],{"type":30,"value":3088},"Key",{"type":24,"tag":155,"props":3090,"children":3091},{"style":173},[3092],{"type":30,"value":551},{"type":24,"tag":155,"props":3094,"children":3096},{"style":3095},"--shiki-default:#4C9A91",[3097],{"type":30,"value":3098},"0",{"type":24,"tag":155,"props":3100,"children":3101},{"style":173},[3102],{"type":30,"value":3103},")]\n",{"type":24,"tag":155,"props":3105,"children":3106},{"class":157,"line":252},[3107,3111,3116,3121,3125,3129,3133,3137,3141,3145,3149,3154],{"type":24,"tag":155,"props":3108,"children":3109},{"style":237},[3110],{"type":30,"value":309},{"type":24,"tag":155,"props":3112,"children":3113},{"style":161},[3114],{"type":30,"value":3115}," int",{"type":24,"tag":155,"props":3117,"children":3118},{"style":327},[3119],{"type":30,"value":3120}," Version",{"type":24,"tag":155,"props":3122,"children":3123},{"style":173},[3124],{"type":30,"value":1178},{"type":24,"tag":155,"props":3126,"children":3127},{"style":237},[3128],{"type":30,"value":1183},{"type":24,"tag":155,"props":3130,"children":3131},{"style":173},[3132],{"type":30,"value":1188},{"type":24,"tag":155,"props":3134,"children":3135},{"style":237},[3136],{"type":30,"value":1198},{"type":24,"tag":155,"props":3138,"children":3139},{"style":173},[3140],{"type":30,"value":1188},{"type":24,"tag":155,"props":3142,"children":3143},{"style":173},[3144],{"type":30,"value":1207},{"type":24,"tag":155,"props":3146,"children":3147},{"style":173},[3148],{"type":30,"value":443},{"type":24,"tag":155,"props":3150,"children":3151},{"style":3095},[3152],{"type":30,"value":3153}," 2",{"type":24,"tag":155,"props":3155,"children":3156},{"style":173},[3157],{"type":30,"value":186},{"type":24,"tag":155,"props":3159,"children":3160},{"class":157,"line":260},[3161],{"type":24,"tag":155,"props":3162,"children":3163},{"emptyLinePlaceholder":227},[3164],{"type":30,"value":230},{"type":24,"tag":155,"props":3166,"children":3167},{"class":157,"line":294},[3168,3172,3176,3180,3185],{"type":24,"tag":155,"props":3169,"children":3170},{"style":173},[3171],{"type":30,"value":3083},{"type":24,"tag":155,"props":3173,"children":3174},{"style":167},[3175],{"type":30,"value":3088},{"type":24,"tag":155,"props":3177,"children":3178},{"style":173},[3179],{"type":30,"value":551},{"type":24,"tag":155,"props":3181,"children":3182},{"style":3095},[3183],{"type":30,"value":3184},"1",{"type":24,"tag":155,"props":3186,"children":3187},{"style":173},[3188],{"type":30,"value":3103},{"type":24,"tag":155,"props":3190,"children":3191},{"class":157,"line":303},[3192,3196,3201,3206,3210,3214,3218,3222,3226,3230,3234,3238],{"type":24,"tag":155,"props":3193,"children":3194},{"style":237},[3195],{"type":30,"value":309},{"type":24,"tag":155,"props":3197,"children":3198},{"style":167},[3199],{"type":30,"value":3200}," MusicMetadataModel",{"type":24,"tag":155,"props":3202,"children":3203},{"style":327},[3204],{"type":30,"value":3205}," Metadata",{"type":24,"tag":155,"props":3207,"children":3208},{"style":173},[3209],{"type":30,"value":1178},{"type":24,"tag":155,"props":3211,"children":3212},{"style":237},[3213],{"type":30,"value":1183},{"type":24,"tag":155,"props":3215,"children":3216},{"style":173},[3217],{"type":30,"value":1188},{"type":24,"tag":155,"props":3219,"children":3220},{"style":237},[3221],{"type":30,"value":1198},{"type":24,"tag":155,"props":3223,"children":3224},{"style":173},[3225],{"type":30,"value":1188},{"type":24,"tag":155,"props":3227,"children":3228},{"style":173},[3229],{"type":30,"value":1207},{"type":24,"tag":155,"props":3231,"children":3232},{"style":173},[3233],{"type":30,"value":443},{"type":24,"tag":155,"props":3235,"children":3236},{"style":237},[3237],{"type":30,"value":506},{"type":24,"tag":155,"props":3239,"children":3240},{"style":173},[3241],{"type":30,"value":516},{"type":24,"tag":155,"props":3243,"children":3244},{"class":157,"line":337},[3245],{"type":24,"tag":155,"props":3246,"children":3247},{"emptyLinePlaceholder":227},[3248],{"type":30,"value":230},{"type":24,"tag":155,"props":3250,"children":3251},{"class":157,"line":345},[3252,3256,3260,3264,3269],{"type":24,"tag":155,"props":3253,"children":3254},{"style":173},[3255],{"type":30,"value":3083},{"type":24,"tag":155,"props":3257,"children":3258},{"style":167},[3259],{"type":30,"value":3088},{"type":24,"tag":155,"props":3261,"children":3262},{"style":173},[3263],{"type":30,"value":551},{"type":24,"tag":155,"props":3265,"children":3266},{"style":3095},[3267],{"type":30,"value":3268},"2",{"type":24,"tag":155,"props":3270,"children":3271},{"style":173},[3272],{"type":30,"value":3103},{"type":24,"tag":155,"props":3274,"children":3275},{"class":157,"line":456},[3276,3280,3285,3290,3294,3298,3302,3306,3310,3314,3318,3322],{"type":24,"tag":155,"props":3277,"children":3278},{"style":237},[3279],{"type":30,"value":309},{"type":24,"tag":155,"props":3281,"children":3282},{"style":167},[3283],{"type":30,"value":3284}," MusicSettingsModel",{"type":24,"tag":155,"props":3286,"children":3287},{"style":327},[3288],{"type":30,"value":3289}," Settings",{"type":24,"tag":155,"props":3291,"children":3292},{"style":173},[3293],{"type":30,"value":1178},{"type":24,"tag":155,"props":3295,"children":3296},{"style":237},[3297],{"type":30,"value":1183},{"type":24,"tag":155,"props":3299,"children":3300},{"style":173},[3301],{"type":30,"value":1188},{"type":24,"tag":155,"props":3303,"children":3304},{"style":237},[3305],{"type":30,"value":1198},{"type":24,"tag":155,"props":3307,"children":3308},{"style":173},[3309],{"type":30,"value":1188},{"type":24,"tag":155,"props":3311,"children":3312},{"style":173},[3313],{"type":30,"value":1207},{"type":24,"tag":155,"props":3315,"children":3316},{"style":173},[3317],{"type":30,"value":443},{"type":24,"tag":155,"props":3319,"children":3320},{"style":237},[3321],{"type":30,"value":506},{"type":24,"tag":155,"props":3323,"children":3324},{"style":173},[3325],{"type":30,"value":516},{"type":24,"tag":155,"props":3327,"children":3328},{"class":157,"line":465},[3329],{"type":24,"tag":155,"props":3330,"children":3331},{"emptyLinePlaceholder":227},[3332],{"type":30,"value":230},{"type":24,"tag":155,"props":3334,"children":3335},{"class":157,"line":519},[3336,3340,3344,3348,3353],{"type":24,"tag":155,"props":3337,"children":3338},{"style":173},[3339],{"type":30,"value":3083},{"type":24,"tag":155,"props":3341,"children":3342},{"style":167},[3343],{"type":30,"value":3088},{"type":24,"tag":155,"props":3345,"children":3346},{"style":173},[3347],{"type":30,"value":551},{"type":24,"tag":155,"props":3349,"children":3350},{"style":3095},[3351],{"type":30,"value":3352},"3",{"type":24,"tag":155,"props":3354,"children":3355},{"style":173},[3356],{"type":30,"value":3103},{"type":24,"tag":155,"props":3358,"children":3359},{"class":157,"line":540},[3360,3364,3369,3374,3379,3383,3387,3391,3395,3399,3403,3407],{"type":24,"tag":155,"props":3361,"children":3362},{"style":237},[3363],{"type":30,"value":309},{"type":24,"tag":155,"props":3365,"children":3366},{"style":167},[3367],{"type":30,"value":3368}," TrackDataModel",{"type":24,"tag":155,"props":3370,"children":3371},{"style":173},[3372],{"type":30,"value":3373},"[]",{"type":24,"tag":155,"props":3375,"children":3376},{"style":327},[3377],{"type":30,"value":3378}," Tracks",{"type":24,"tag":155,"props":3380,"children":3381},{"style":173},[3382],{"type":30,"value":1178},{"type":24,"tag":155,"props":3384,"children":3385},{"style":237},[3386],{"type":30,"value":1183},{"type":24,"tag":155,"props":3388,"children":3389},{"style":173},[3390],{"type":30,"value":1188},{"type":24,"tag":155,"props":3392,"children":3393},{"style":237},[3394],{"type":30,"value":1198},{"type":24,"tag":155,"props":3396,"children":3397},{"style":173},[3398],{"type":30,"value":1188},{"type":24,"tag":155,"props":3400,"children":3401},{"style":173},[3402],{"type":30,"value":1207},{"type":24,"tag":155,"props":3404,"children":3405},{"style":173},[3406],{"type":30,"value":443},{"type":24,"tag":155,"props":3408,"children":3409},{"style":173},[3410],{"type":30,"value":3411}," [];\n",{"type":24,"tag":155,"props":3413,"children":3414},{"class":157,"line":563},[3415],{"type":24,"tag":155,"props":3416,"children":3417},{"style":173},[3418],{"type":30,"value":694},{"type":24,"tag":32,"props":3420,"children":3421},{},[3422,3424,3429,3431,3436],{"type":30,"value":3423},"ここで一番重要なのが",{"type":24,"tag":46,"props":3425,"children":3427},{"className":3426},[],[3428],{"type":30,"value":2805},{"type":30,"value":3430},"プロパティです。後述しますが、これは",{"type":24,"tag":38,"props":3432,"children":3433},{},[3434],{"type":30,"value":3435},"書き込んだ時のフォーマットバージョン",{"type":30,"value":3437},"を自己申告するためのフィールドで、将来のフォーマット進化の鍵になります。",{"type":24,"tag":32,"props":3439,"children":3440},{},[3441,3443,3449,3451,3457,3459,3465,3466,3472,3473,3479,3481,3487,3489,3495],{"type":30,"value":3442},"Trackの中身はもっとツリーが深く、",{"type":24,"tag":46,"props":3444,"children":3446},{"className":3445},[],[3447],{"type":30,"value":3448},"ComponentDataModel",{"type":30,"value":3450},"を基底にした",{"type":24,"tag":46,"props":3452,"children":3454},{"className":3453},[],[3455],{"type":30,"value":3456},"NoteDataModel",{"type":30,"value":3458}," / ",{"type":24,"tag":46,"props":3460,"children":3462},{"className":3461},[],[3463],{"type":30,"value":3464},"RestDataModel",{"type":30,"value":3458},{"type":24,"tag":46,"props":3467,"children":3469},{"className":3468},[],[3470],{"type":30,"value":3471},"TieDataModel",{"type":30,"value":3458},{"type":24,"tag":46,"props":3474,"children":3476},{"className":3475},[],[3477],{"type":30,"value":3478},"TupletDataModel",{"type":30,"value":3480},"のサブクラスが入っています。MessagePackでは",{"type":24,"tag":46,"props":3482,"children":3484},{"className":3483},[],[3485],{"type":30,"value":3486},"[Union]",{"type":30,"value":3488},"属性で多態を表現する方法と、サブクラスを判別する",{"type":24,"tag":46,"props":3490,"children":3492},{"className":3491},[],[3493],{"type":30,"value":3494},"Type",{"type":30,"value":3496},"フィールドを持たせる方法がありますが、PIMでは前者を使っています。",{"type":24,"tag":25,"props":3498,"children":3500},{"id":3499},"key番号は追記のみ絶対に削除しない",[3501,3506],{"type":24,"tag":46,"props":3502,"children":3504},{"className":3503},[],[3505],{"type":30,"value":2813},{"type":30,"value":3507},"番号は追記のみ、絶対に削除しない",{"type":24,"tag":32,"props":3509,"children":3510},{},[3511],{"type":30,"value":3512},"DBの主キーもそうですが、キーはイミュータブルな運用としています。",{"type":24,"tag":32,"props":3514,"children":3515},{},[3516,3522,3523,3529,3530,3536,3538,3543],{"type":24,"tag":46,"props":3517,"children":3519},{"className":3518},[],[3520],{"type":30,"value":3521},"[Key(0)]",{"type":30,"value":2864},{"type":24,"tag":46,"props":3524,"children":3526},{"className":3525},[],[3527],{"type":30,"value":3528},"[Key(1)]",{"type":30,"value":2864},{"type":24,"tag":46,"props":3531,"children":3533},{"className":3532},[],[3534],{"type":30,"value":3535},"[Key(2)]",{"type":30,"value":3537},"... の番号は、一度付けたら",{"type":24,"tag":38,"props":3539,"children":3540},{},[3541],{"type":30,"value":3542},"絶対に変えてはいけません",{"type":30,"value":3544},"。番号はバイナリ内の物理的な位置そのもので、番号を変えると古いセーブデータが読み込めなくなります。",{"type":24,"tag":32,"props":3546,"children":3547},{},[3548,3550,3555],{"type":30,"value":3549},"フィールドを増やすときはどうするか。次のように常に",{"type":24,"tag":38,"props":3551,"children":3552},{},[3553],{"type":30,"value":3554},"新しい番号を末尾に追加",{"type":30,"value":3556},"します。",{"type":24,"tag":145,"props":3558,"children":3560},{"className":147,"code":3559,"language":149,"meta":8,"style":8},"// 悪い例: 既存の番号を詰め直してしまう\n[Key(0)] public int Version { get; set; }\n[Key(1)] public MusicSettingsModel Settings { get; set; }  // Metadataを消して番号を詰めた\n[Key(2)] public TrackDataModel[] Tracks { get; set; }\n\n// 良い例: 既存番号はそのまま、新規は末尾に追加\n[Key(0)] public int Version { get; set; }\n[Key(1)] public MusicMetadataModel Metadata { get; set; }\n[Key(2)] public MusicSettingsModel Settings { get; set; }\n[Key(3)] public TrackDataModel[] Tracks { get; set; }\n[Key(4)] public string? Comment { get; set; }  // 新規追加\n",[3561],{"type":24,"tag":46,"props":3562,"children":3563},{"__ignoreMap":8},[3564,3572,3634,3698,3761,3768,3776,3835,3894,3953,4016],{"type":24,"tag":155,"props":3565,"children":3566},{"class":157,"line":18},[3567],{"type":24,"tag":155,"props":3568,"children":3569},{"style":1411},[3570],{"type":30,"value":3571},"// 悪い例: 既存の番号を詰め直してしまう\n",{"type":24,"tag":155,"props":3573,"children":3574},{"class":157,"line":189},[3575,3579,3583,3587,3591,3596,3601,3605,3609,3613,3617,3621,3625,3629],{"type":24,"tag":155,"props":3576,"children":3577},{"style":173},[3578],{"type":30,"value":3042},{"type":24,"tag":155,"props":3580,"children":3581},{"style":167},[3582],{"type":30,"value":3088},{"type":24,"tag":155,"props":3584,"children":3585},{"style":173},[3586],{"type":30,"value":551},{"type":24,"tag":155,"props":3588,"children":3589},{"style":3095},[3590],{"type":30,"value":3098},{"type":24,"tag":155,"props":3592,"children":3593},{"style":173},[3594],{"type":30,"value":3595},")]",{"type":24,"tag":155,"props":3597,"children":3598},{"style":237},[3599],{"type":30,"value":3600}," public",{"type":24,"tag":155,"props":3602,"children":3603},{"style":161},[3604],{"type":30,"value":3115},{"type":24,"tag":155,"props":3606,"children":3607},{"style":479},[3608],{"type":30,"value":3120},{"type":24,"tag":155,"props":3610,"children":3611},{"style":173},[3612],{"type":30,"value":1178},{"type":24,"tag":155,"props":3614,"children":3615},{"style":479},[3616],{"type":30,"value":1183},{"type":24,"tag":155,"props":3618,"children":3619},{"style":173},[3620],{"type":30,"value":1188},{"type":24,"tag":155,"props":3622,"children":3623},{"style":479},[3624],{"type":30,"value":1198},{"type":24,"tag":155,"props":3626,"children":3627},{"style":173},[3628],{"type":30,"value":1188},{"type":24,"tag":155,"props":3630,"children":3631},{"style":173},[3632],{"type":30,"value":3633}," }\n",{"type":24,"tag":155,"props":3635,"children":3636},{"class":157,"line":223},[3637,3641,3645,3649,3653,3657,3661,3665,3669,3673,3677,3681,3685,3689,3693],{"type":24,"tag":155,"props":3638,"children":3639},{"style":173},[3640],{"type":30,"value":3042},{"type":24,"tag":155,"props":3642,"children":3643},{"style":167},[3644],{"type":30,"value":3088},{"type":24,"tag":155,"props":3646,"children":3647},{"style":173},[3648],{"type":30,"value":551},{"type":24,"tag":155,"props":3650,"children":3651},{"style":3095},[3652],{"type":30,"value":3184},{"type":24,"tag":155,"props":3654,"children":3655},{"style":173},[3656],{"type":30,"value":3595},{"type":24,"tag":155,"props":3658,"children":3659},{"style":237},[3660],{"type":30,"value":3600},{"type":24,"tag":155,"props":3662,"children":3663},{"style":479},[3664],{"type":30,"value":3284},{"type":24,"tag":155,"props":3666,"children":3667},{"style":479},[3668],{"type":30,"value":3289},{"type":24,"tag":155,"props":3670,"children":3671},{"style":173},[3672],{"type":30,"value":1178},{"type":24,"tag":155,"props":3674,"children":3675},{"style":479},[3676],{"type":30,"value":1183},{"type":24,"tag":155,"props":3678,"children":3679},{"style":173},[3680],{"type":30,"value":1188},{"type":24,"tag":155,"props":3682,"children":3683},{"style":479},[3684],{"type":30,"value":1198},{"type":24,"tag":155,"props":3686,"children":3687},{"style":173},[3688],{"type":30,"value":1188},{"type":24,"tag":155,"props":3690,"children":3691},{"style":173},[3692],{"type":30,"value":1207},{"type":24,"tag":155,"props":3694,"children":3695},{"style":1411},[3696],{"type":30,"value":3697},"  // Metadataを消して番号を詰めた\n",{"type":24,"tag":155,"props":3699,"children":3700},{"class":157,"line":233},[3701,3705,3709,3713,3717,3721,3725,3729,3733,3737,3741,3745,3749,3753,3757],{"type":24,"tag":155,"props":3702,"children":3703},{"style":173},[3704],{"type":30,"value":3042},{"type":24,"tag":155,"props":3706,"children":3707},{"style":167},[3708],{"type":30,"value":3088},{"type":24,"tag":155,"props":3710,"children":3711},{"style":173},[3712],{"type":30,"value":551},{"type":24,"tag":155,"props":3714,"children":3715},{"style":3095},[3716],{"type":30,"value":3268},{"type":24,"tag":155,"props":3718,"children":3719},{"style":173},[3720],{"type":30,"value":3595},{"type":24,"tag":155,"props":3722,"children":3723},{"style":237},[3724],{"type":30,"value":3600},{"type":24,"tag":155,"props":3726,"children":3727},{"style":479},[3728],{"type":30,"value":3368},{"type":24,"tag":155,"props":3730,"children":3731},{"style":173},[3732],{"type":30,"value":3373},{"type":24,"tag":155,"props":3734,"children":3735},{"style":479},[3736],{"type":30,"value":3378},{"type":24,"tag":155,"props":3738,"children":3739},{"style":173},[3740],{"type":30,"value":1178},{"type":24,"tag":155,"props":3742,"children":3743},{"style":479},[3744],{"type":30,"value":1183},{"type":24,"tag":155,"props":3746,"children":3747},{"style":173},[3748],{"type":30,"value":1188},{"type":24,"tag":155,"props":3750,"children":3751},{"style":479},[3752],{"type":30,"value":1198},{"type":24,"tag":155,"props":3754,"children":3755},{"style":173},[3756],{"type":30,"value":1188},{"type":24,"tag":155,"props":3758,"children":3759},{"style":173},[3760],{"type":30,"value":3633},{"type":24,"tag":155,"props":3762,"children":3763},{"class":157,"line":252},[3764],{"type":24,"tag":155,"props":3765,"children":3766},{"emptyLinePlaceholder":227},[3767],{"type":30,"value":230},{"type":24,"tag":155,"props":3769,"children":3770},{"class":157,"line":260},[3771],{"type":24,"tag":155,"props":3772,"children":3773},{"style":1411},[3774],{"type":30,"value":3775},"// 良い例: 既存番号はそのまま、新規は末尾に追加\n",{"type":24,"tag":155,"props":3777,"children":3778},{"class":157,"line":294},[3779,3783,3787,3791,3795,3799,3803,3807,3811,3815,3819,3823,3827,3831],{"type":24,"tag":155,"props":3780,"children":3781},{"style":173},[3782],{"type":30,"value":3042},{"type":24,"tag":155,"props":3784,"children":3785},{"style":167},[3786],{"type":30,"value":3088},{"type":24,"tag":155,"props":3788,"children":3789},{"style":173},[3790],{"type":30,"value":551},{"type":24,"tag":155,"props":3792,"children":3793},{"style":3095},[3794],{"type":30,"value":3098},{"type":24,"tag":155,"props":3796,"children":3797},{"style":173},[3798],{"type":30,"value":3595},{"type":24,"tag":155,"props":3800,"children":3801},{"style":237},[3802],{"type":30,"value":3600},{"type":24,"tag":155,"props":3804,"children":3805},{"style":161},[3806],{"type":30,"value":3115},{"type":24,"tag":155,"props":3808,"children":3809},{"style":479},[3810],{"type":30,"value":3120},{"type":24,"tag":155,"props":3812,"children":3813},{"style":173},[3814],{"type":30,"value":1178},{"type":24,"tag":155,"props":3816,"children":3817},{"style":479},[3818],{"type":30,"value":1183},{"type":24,"tag":155,"props":3820,"children":3821},{"style":173},[3822],{"type":30,"value":1188},{"type":24,"tag":155,"props":3824,"children":3825},{"style":479},[3826],{"type":30,"value":1198},{"type":24,"tag":155,"props":3828,"children":3829},{"style":173},[3830],{"type":30,"value":1188},{"type":24,"tag":155,"props":3832,"children":3833},{"style":173},[3834],{"type":30,"value":3633},{"type":24,"tag":155,"props":3836,"children":3837},{"class":157,"line":303},[3838,3842,3846,3850,3854,3858,3862,3866,3870,3874,3878,3882,3886,3890],{"type":24,"tag":155,"props":3839,"children":3840},{"style":173},[3841],{"type":30,"value":3042},{"type":24,"tag":155,"props":3843,"children":3844},{"style":167},[3845],{"type":30,"value":3088},{"type":24,"tag":155,"props":3847,"children":3848},{"style":173},[3849],{"type":30,"value":551},{"type":24,"tag":155,"props":3851,"children":3852},{"style":3095},[3853],{"type":30,"value":3184},{"type":24,"tag":155,"props":3855,"children":3856},{"style":173},[3857],{"type":30,"value":3595},{"type":24,"tag":155,"props":3859,"children":3860},{"style":237},[3861],{"type":30,"value":3600},{"type":24,"tag":155,"props":3863,"children":3864},{"style":479},[3865],{"type":30,"value":3200},{"type":24,"tag":155,"props":3867,"children":3868},{"style":479},[3869],{"type":30,"value":3205},{"type":24,"tag":155,"props":3871,"children":3872},{"style":173},[3873],{"type":30,"value":1178},{"type":24,"tag":155,"props":3875,"children":3876},{"style":479},[3877],{"type":30,"value":1183},{"type":24,"tag":155,"props":3879,"children":3880},{"style":173},[3881],{"type":30,"value":1188},{"type":24,"tag":155,"props":3883,"children":3884},{"style":479},[3885],{"type":30,"value":1198},{"type":24,"tag":155,"props":3887,"children":3888},{"style":173},[3889],{"type":30,"value":1188},{"type":24,"tag":155,"props":3891,"children":3892},{"style":173},[3893],{"type":30,"value":3633},{"type":24,"tag":155,"props":3895,"children":3896},{"class":157,"line":337},[3897,3901,3905,3909,3913,3917,3921,3925,3929,3933,3937,3941,3945,3949],{"type":24,"tag":155,"props":3898,"children":3899},{"style":173},[3900],{"type":30,"value":3042},{"type":24,"tag":155,"props":3902,"children":3903},{"style":167},[3904],{"type":30,"value":3088},{"type":24,"tag":155,"props":3906,"children":3907},{"style":173},[3908],{"type":30,"value":551},{"type":24,"tag":155,"props":3910,"children":3911},{"style":3095},[3912],{"type":30,"value":3268},{"type":24,"tag":155,"props":3914,"children":3915},{"style":173},[3916],{"type":30,"value":3595},{"type":24,"tag":155,"props":3918,"children":3919},{"style":237},[3920],{"type":30,"value":3600},{"type":24,"tag":155,"props":3922,"children":3923},{"style":479},[3924],{"type":30,"value":3284},{"type":24,"tag":155,"props":3926,"children":3927},{"style":479},[3928],{"type":30,"value":3289},{"type":24,"tag":155,"props":3930,"children":3931},{"style":173},[3932],{"type":30,"value":1178},{"type":24,"tag":155,"props":3934,"children":3935},{"style":479},[3936],{"type":30,"value":1183},{"type":24,"tag":155,"props":3938,"children":3939},{"style":173},[3940],{"type":30,"value":1188},{"type":24,"tag":155,"props":3942,"children":3943},{"style":479},[3944],{"type":30,"value":1198},{"type":24,"tag":155,"props":3946,"children":3947},{"style":173},[3948],{"type":30,"value":1188},{"type":24,"tag":155,"props":3950,"children":3951},{"style":173},[3952],{"type":30,"value":3633},{"type":24,"tag":155,"props":3954,"children":3955},{"class":157,"line":345},[3956,3960,3964,3968,3972,3976,3980,3984,3988,3992,3996,4000,4004,4008,4012],{"type":24,"tag":155,"props":3957,"children":3958},{"style":173},[3959],{"type":30,"value":3042},{"type":24,"tag":155,"props":3961,"children":3962},{"style":167},[3963],{"type":30,"value":3088},{"type":24,"tag":155,"props":3965,"children":3966},{"style":173},[3967],{"type":30,"value":551},{"type":24,"tag":155,"props":3969,"children":3970},{"style":3095},[3971],{"type":30,"value":3352},{"type":24,"tag":155,"props":3973,"children":3974},{"style":173},[3975],{"type":30,"value":3595},{"type":24,"tag":155,"props":3977,"children":3978},{"style":237},[3979],{"type":30,"value":3600},{"type":24,"tag":155,"props":3981,"children":3982},{"style":479},[3983],{"type":30,"value":3368},{"type":24,"tag":155,"props":3985,"children":3986},{"style":173},[3987],{"type":30,"value":3373},{"type":24,"tag":155,"props":3989,"children":3990},{"style":479},[3991],{"type":30,"value":3378},{"type":24,"tag":155,"props":3993,"children":3994},{"style":173},[3995],{"type":30,"value":1178},{"type":24,"tag":155,"props":3997,"children":3998},{"style":479},[3999],{"type":30,"value":1183},{"type":24,"tag":155,"props":4001,"children":4002},{"style":173},[4003],{"type":30,"value":1188},{"type":24,"tag":155,"props":4005,"children":4006},{"style":479},[4007],{"type":30,"value":1198},{"type":24,"tag":155,"props":4009,"children":4010},{"style":173},[4011],{"type":30,"value":1188},{"type":24,"tag":155,"props":4013,"children":4014},{"style":173},[4015],{"type":30,"value":3633},{"type":24,"tag":155,"props":4017,"children":4018},{"class":157,"line":456},[4019,4023,4027,4031,4036,4040,4044,4048,4052,4057,4061,4065,4069,4073,4077,4081],{"type":24,"tag":155,"props":4020,"children":4021},{"style":173},[4022],{"type":30,"value":3042},{"type":24,"tag":155,"props":4024,"children":4025},{"style":167},[4026],{"type":30,"value":3088},{"type":24,"tag":155,"props":4028,"children":4029},{"style":173},[4030],{"type":30,"value":551},{"type":24,"tag":155,"props":4032,"children":4033},{"style":3095},[4034],{"type":30,"value":4035},"4",{"type":24,"tag":155,"props":4037,"children":4038},{"style":173},[4039],{"type":30,"value":3595},{"type":24,"tag":155,"props":4041,"children":4042},{"style":237},[4043],{"type":30,"value":3600},{"type":24,"tag":155,"props":4045,"children":4046},{"style":161},[4047],{"type":30,"value":429},{"type":24,"tag":155,"props":4049,"children":4050},{"style":237},[4051],{"type":30,"value":324},{"type":24,"tag":155,"props":4053,"children":4054},{"style":479},[4055],{"type":30,"value":4056}," Comment",{"type":24,"tag":155,"props":4058,"children":4059},{"style":173},[4060],{"type":30,"value":1178},{"type":24,"tag":155,"props":4062,"children":4063},{"style":479},[4064],{"type":30,"value":1183},{"type":24,"tag":155,"props":4066,"children":4067},{"style":173},[4068],{"type":30,"value":1188},{"type":24,"tag":155,"props":4070,"children":4071},{"style":479},[4072],{"type":30,"value":1198},{"type":24,"tag":155,"props":4074,"children":4075},{"style":173},[4076],{"type":30,"value":1188},{"type":24,"tag":155,"props":4078,"children":4079},{"style":173},[4080],{"type":30,"value":1207},{"type":24,"tag":155,"props":4082,"children":4083},{"style":1411},[4084],{"type":30,"value":4085},"  // 新規追加\n",{"type":24,"tag":32,"props":4087,"children":4088},{},[4089,4091,4096],{"type":30,"value":4090},"削除したいフィールドがあっても、番号は",{"type":24,"tag":38,"props":4092,"children":4093},{},[4094],{"type":30,"value":4095},"空き番として予約",{"type":30,"value":4097},"しておき、コメントで「deprecated」と書いておくのが安全です。",{"type":24,"tag":25,"props":4099,"children":4101},{"id":4100},"versionフィールドで将来のフォーマット変更に備える",[4102],{"type":30,"value":4103},"Versionフィールドで将来のフォーマット変更に備える",{"type":24,"tag":32,"props":4105,"children":4106},{},[4107,4109,4115,4117,4123,4125,4130,4132,4137],{"type":30,"value":4108},"たとえば「トラックに色を付けたい」という要件が来たとします。単純に",{"type":24,"tag":46,"props":4110,"children":4112},{"className":4111},[],[4113],{"type":30,"value":4114},"TrackDataModel",{"type":30,"value":4116},"に",{"type":24,"tag":46,"props":4118,"children":4120},{"className":4119},[],[4121],{"type":30,"value":4122},"[Key(n)] int ColorHex",{"type":30,"value":4124},"を足すだけでも新しい番号なら動きますが、PICOMでは",{"type":24,"tag":38,"props":4126,"children":4127},{},[4128],{"type":30,"value":4129},"モデル自体が大きく変わる将来",{"type":30,"value":4131},"にも備えて",{"type":24,"tag":46,"props":4133,"children":4135},{"className":4134},[],[4136],{"type":30,"value":2805},{"type":30,"value":4138},"を数値で持たせています。",{"type":24,"tag":145,"props":4140,"children":4142},{"className":147,"code":4141,"language":149,"meta":8,"style":8},"public (ReadResult resultType, Music? music) Read(byte[] data, int musicId)\n{\n    MusicDataModel model;\n    try\n    {\n        model = MessagePackSerializer.Deserialize\u003CMusicDataModel>(data);\n        Console.WriteLine($\"[PIM] Read: deserialized OK, version = {model.Version}, tracks = {model.Tracks.Length}\");\n    }\n    catch (Exception ex)\n    {\n        Console.WriteLine($\"[PIM] Read: deserialize error: {ex.Message}\");\n        return (ReadResult.FormatError, null);\n    }\n    // ... model.Version に応じて分岐する余地がある\n}\n",[4143],{"type":24,"tag":46,"props":4144,"children":4145},{"__ignoreMap":8},[4146,4229,4236,4253,4261,4268,4315,4412,4419,4445,4452,4510,4547,4554,4562],{"type":24,"tag":155,"props":4147,"children":4148},{"class":157,"line":18},[4149,4153,4157,4162,4167,4171,4176,4180,4185,4189,4194,4198,4203,4207,4212,4216,4220,4225],{"type":24,"tag":155,"props":4150,"children":4151},{"style":237},[4152],{"type":30,"value":266},{"type":24,"tag":155,"props":4154,"children":4155},{"style":173},[4156],{"type":30,"value":476},{"type":24,"tag":155,"props":4158,"children":4159},{"style":167},[4160],{"type":30,"value":4161},"ReadResult",{"type":24,"tag":155,"props":4163,"children":4164},{"style":327},[4165],{"type":30,"value":4166}," resultType",{"type":24,"tag":155,"props":4168,"children":4169},{"style":173},[4170],{"type":30,"value":396},{"type":24,"tag":155,"props":4172,"children":4173},{"style":167},[4174],{"type":30,"value":4175}," Music",{"type":24,"tag":155,"props":4177,"children":4178},{"style":173},[4179],{"type":30,"value":324},{"type":24,"tag":155,"props":4181,"children":4182},{"style":327},[4183],{"type":30,"value":4184}," music",{"type":24,"tag":155,"props":4186,"children":4187},{"style":173},[4188],{"type":30,"value":496},{"type":24,"tag":155,"props":4190,"children":4191},{"style":327},[4192],{"type":30,"value":4193}," Read",{"type":24,"tag":155,"props":4195,"children":4196},{"style":173},[4197],{"type":30,"value":551},{"type":24,"tag":155,"props":4199,"children":4200},{"style":161},[4201],{"type":30,"value":4202},"byte",{"type":24,"tag":155,"props":4204,"children":4205},{"style":173},[4206],{"type":30,"value":3373},{"type":24,"tag":155,"props":4208,"children":4209},{"style":327},[4210],{"type":30,"value":4211}," data",{"type":24,"tag":155,"props":4213,"children":4214},{"style":173},[4215],{"type":30,"value":396},{"type":24,"tag":155,"props":4217,"children":4218},{"style":161},[4219],{"type":30,"value":3115},{"type":24,"tag":155,"props":4221,"children":4222},{"style":327},[4223],{"type":30,"value":4224}," musicId",{"type":24,"tag":155,"props":4226,"children":4227},{"style":173},[4228],{"type":30,"value":453},{"type":24,"tag":155,"props":4230,"children":4231},{"class":157,"line":189},[4232],{"type":24,"tag":155,"props":4233,"children":4234},{"style":173},[4235],{"type":30,"value":300},{"type":24,"tag":155,"props":4237,"children":4238},{"class":157,"line":223},[4239,4244,4249],{"type":24,"tag":155,"props":4240,"children":4241},{"style":167},[4242],{"type":30,"value":4243},"    MusicDataModel",{"type":24,"tag":155,"props":4245,"children":4246},{"style":327},[4247],{"type":30,"value":4248}," model",{"type":24,"tag":155,"props":4250,"children":4251},{"style":173},[4252],{"type":30,"value":186},{"type":24,"tag":155,"props":4254,"children":4255},{"class":157,"line":233},[4256],{"type":24,"tag":155,"props":4257,"children":4258},{"style":161},[4259],{"type":30,"value":4260},"    try\n",{"type":24,"tag":155,"props":4262,"children":4263},{"class":157,"line":252},[4264],{"type":24,"tag":155,"props":4265,"children":4266},{"style":173},[4267],{"type":30,"value":462},{"type":24,"tag":155,"props":4269,"children":4270},{"class":157,"line":260},[4271,4276,4280,4285,4289,4294,4298,4302,4306,4311],{"type":24,"tag":155,"props":4272,"children":4273},{"style":479},[4274],{"type":30,"value":4275},"        model",{"type":24,"tag":155,"props":4277,"children":4278},{"style":173},[4279],{"type":30,"value":443},{"type":24,"tag":155,"props":4281,"children":4282},{"style":479},[4283],{"type":30,"value":4284}," MessagePackSerializer",{"type":24,"tag":155,"props":4286,"children":4287},{"style":173},[4288],{"type":30,"value":176},{"type":24,"tag":155,"props":4290,"children":4291},{"style":327},[4292],{"type":30,"value":4293},"Deserialize",{"type":24,"tag":155,"props":4295,"children":4296},{"style":173},[4297],{"type":30,"value":366},{"type":24,"tag":155,"props":4299,"children":4300},{"style":167},[4301],{"type":30,"value":3012},{"type":24,"tag":155,"props":4303,"children":4304},{"style":173},[4305],{"type":30,"value":376},{"type":24,"tag":155,"props":4307,"children":4308},{"style":479},[4309],{"type":30,"value":4310},"data",{"type":24,"tag":155,"props":4312,"children":4313},{"style":173},[4314],{"type":30,"value":560},{"type":24,"tag":155,"props":4316,"children":4317},{"class":157,"line":294},[4318,4323,4327,4332,4336,4341,4346,4351,4356,4360,4364,4369,4374,4378,4382,4386,4391,4395,4400,4404,4408],{"type":24,"tag":155,"props":4319,"children":4320},{"style":479},[4321],{"type":30,"value":4322},"        Console",{"type":24,"tag":155,"props":4324,"children":4325},{"style":173},[4326],{"type":30,"value":176},{"type":24,"tag":155,"props":4328,"children":4329},{"style":327},[4330],{"type":30,"value":4331},"WriteLine",{"type":24,"tag":155,"props":4333,"children":4334},{"style":173},[4335],{"type":30,"value":551},{"type":24,"tag":155,"props":4337,"children":4338},{"style":768},[4339],{"type":30,"value":4340},"$\"",{"type":24,"tag":155,"props":4342,"children":4343},{"style":2239},[4344],{"type":30,"value":4345},"[PIM] Read: deserialized OK, version = ",{"type":24,"tag":155,"props":4347,"children":4348},{"style":173},[4349],{"type":30,"value":4350},"{",{"type":24,"tag":155,"props":4352,"children":4353},{"style":2239},[4354],{"type":30,"value":4355},"model",{"type":24,"tag":155,"props":4357,"children":4358},{"style":173},[4359],{"type":30,"value":176},{"type":24,"tag":155,"props":4361,"children":4362},{"style":2239},[4363],{"type":30,"value":2805},{"type":24,"tag":155,"props":4365,"children":4366},{"style":173},[4367],{"type":30,"value":4368},"}",{"type":24,"tag":155,"props":4370,"children":4371},{"style":2239},[4372],{"type":30,"value":4373},", tracks = ",{"type":24,"tag":155,"props":4375,"children":4376},{"style":173},[4377],{"type":30,"value":4350},{"type":24,"tag":155,"props":4379,"children":4380},{"style":2239},[4381],{"type":30,"value":4355},{"type":24,"tag":155,"props":4383,"children":4384},{"style":173},[4385],{"type":30,"value":176},{"type":24,"tag":155,"props":4387,"children":4388},{"style":2239},[4389],{"type":30,"value":4390},"Tracks",{"type":24,"tag":155,"props":4392,"children":4393},{"style":173},[4394],{"type":30,"value":176},{"type":24,"tag":155,"props":4396,"children":4397},{"style":2239},[4398],{"type":30,"value":4399},"Length",{"type":24,"tag":155,"props":4401,"children":4402},{"style":173},[4403],{"type":30,"value":4368},{"type":24,"tag":155,"props":4405,"children":4406},{"style":768},[4407],{"type":30,"value":2246},{"type":24,"tag":155,"props":4409,"children":4410},{"style":173},[4411],{"type":30,"value":560},{"type":24,"tag":155,"props":4413,"children":4414},{"class":157,"line":303},[4415],{"type":24,"tag":155,"props":4416,"children":4417},{"style":173},[4418],{"type":30,"value":569},{"type":24,"tag":155,"props":4420,"children":4421},{"class":157,"line":337},[4422,4427,4431,4436,4441],{"type":24,"tag":155,"props":4423,"children":4424},{"style":161},[4425],{"type":30,"value":4426},"    catch",{"type":24,"tag":155,"props":4428,"children":4429},{"style":173},[4430],{"type":30,"value":476},{"type":24,"tag":155,"props":4432,"children":4433},{"style":167},[4434],{"type":30,"value":4435},"Exception",{"type":24,"tag":155,"props":4437,"children":4438},{"style":327},[4439],{"type":30,"value":4440}," ex",{"type":24,"tag":155,"props":4442,"children":4443},{"style":173},[4444],{"type":30,"value":453},{"type":24,"tag":155,"props":4446,"children":4447},{"class":157,"line":345},[4448],{"type":24,"tag":155,"props":4449,"children":4450},{"style":173},[4451],{"type":30,"value":462},{"type":24,"tag":155,"props":4453,"children":4454},{"class":157,"line":456},[4455,4459,4463,4467,4471,4475,4480,4484,4489,4493,4498,4502,4506],{"type":24,"tag":155,"props":4456,"children":4457},{"style":479},[4458],{"type":30,"value":4322},{"type":24,"tag":155,"props":4460,"children":4461},{"style":173},[4462],{"type":30,"value":176},{"type":24,"tag":155,"props":4464,"children":4465},{"style":327},[4466],{"type":30,"value":4331},{"type":24,"tag":155,"props":4468,"children":4469},{"style":173},[4470],{"type":30,"value":551},{"type":24,"tag":155,"props":4472,"children":4473},{"style":768},[4474],{"type":30,"value":4340},{"type":24,"tag":155,"props":4476,"children":4477},{"style":2239},[4478],{"type":30,"value":4479},"[PIM] Read: deserialize error: ",{"type":24,"tag":155,"props":4481,"children":4482},{"style":173},[4483],{"type":30,"value":4350},{"type":24,"tag":155,"props":4485,"children":4486},{"style":2239},[4487],{"type":30,"value":4488},"ex",{"type":24,"tag":155,"props":4490,"children":4491},{"style":173},[4492],{"type":30,"value":176},{"type":24,"tag":155,"props":4494,"children":4495},{"style":2239},[4496],{"type":30,"value":4497},"Message",{"type":24,"tag":155,"props":4499,"children":4500},{"style":173},[4501],{"type":30,"value":4368},{"type":24,"tag":155,"props":4503,"children":4504},{"style":768},[4505],{"type":30,"value":2246},{"type":24,"tag":155,"props":4507,"children":4508},{"style":173},[4509],{"type":30,"value":560},{"type":24,"tag":155,"props":4511,"children":4512},{"class":157,"line":465},[4513,4518,4522,4526,4530,4535,4539,4543],{"type":24,"tag":155,"props":4514,"children":4515},{"style":161},[4516],{"type":30,"value":4517},"        return",{"type":24,"tag":155,"props":4519,"children":4520},{"style":173},[4521],{"type":30,"value":476},{"type":24,"tag":155,"props":4523,"children":4524},{"style":479},[4525],{"type":30,"value":4161},{"type":24,"tag":155,"props":4527,"children":4528},{"style":173},[4529],{"type":30,"value":176},{"type":24,"tag":155,"props":4531,"children":4532},{"style":479},[4533],{"type":30,"value":4534},"FormatError",{"type":24,"tag":155,"props":4536,"children":4537},{"style":173},[4538],{"type":30,"value":396},{"type":24,"tag":155,"props":4540,"children":4541},{"style":237},[4542],{"type":30,"value":448},{"type":24,"tag":155,"props":4544,"children":4545},{"style":173},[4546],{"type":30,"value":560},{"type":24,"tag":155,"props":4548,"children":4549},{"class":157,"line":519},[4550],{"type":24,"tag":155,"props":4551,"children":4552},{"style":173},[4553],{"type":30,"value":569},{"type":24,"tag":155,"props":4555,"children":4556},{"class":157,"line":540},[4557],{"type":24,"tag":155,"props":4558,"children":4559},{"style":1411},[4560],{"type":30,"value":4561},"    // ... model.Version に応じて分岐する余地がある\n",{"type":24,"tag":155,"props":4563,"children":4564},{"class":157,"line":563},[4565],{"type":24,"tag":155,"props":4566,"children":4567},{"style":173},[4568],{"type":30,"value":694},{"type":24,"tag":32,"props":4570,"children":4571},{},[4572,4574,4579],{"type":30,"value":4573},"現状PIMはv2で統一されていますが、",{"type":24,"tag":46,"props":4575,"children":4577},{"className":4576},[],[4578],{"type":30,"value":2805},{"type":30,"value":4580},"フィールドを持たせているおかげで、いずれv3が必要になったら次のような選択肢を取れます。",{"type":24,"tag":4582,"props":4583,"children":4584},"ol",{},[4585,4610],{"type":24,"tag":921,"props":4586,"children":4587},{},[4588,4594,4595,4601,4603,4608],{"type":24,"tag":46,"props":4589,"children":4591},{"className":4590},[],[4592],{"type":30,"value":4593},"MusicDataModelV2",{"type":30,"value":73},{"type":24,"tag":46,"props":4596,"children":4598},{"className":4597},[],[4599],{"type":30,"value":4600},"MusicDataModelV3",{"type":30,"value":4602},"を別クラスで定義し、読み込み時に",{"type":24,"tag":46,"props":4604,"children":4606},{"className":4605},[],[4607],{"type":30,"value":2805},{"type":30,"value":4609},"を見て振り分ける",{"type":24,"tag":921,"props":4611,"children":4612},{},[4613,4618],{"type":24,"tag":46,"props":4614,"children":4616},{"className":4615},[],[4617],{"type":30,"value":3012},{"type":30,"value":4619},"を大胆に拡張しつつ、v2データは古い部分だけを読み込むフォールバックを用意する",{"type":24,"tag":32,"props":4621,"children":4622},{},[4623,4625,4630],{"type":30,"value":4624},"ここで効いてくるのが、「",{"type":24,"tag":38,"props":4626,"children":4627},{},[4628],{"type":30,"value":4629},"JSONではなくMessagePackであることの副次効果",{"type":30,"value":4631},"」です。MessagePackは余分なフィールドを自動で無視してくれるので、v3で追加したフィールドを持たないv2データを読んでも壊れません。逆に言うと、v3を書いたものをv2のコードで読むときは、追加フィールドが欠落した状態になります。",{"type":24,"tag":2656,"props":4633,"children":4634},{},[4635],{"type":24,"tag":32,"props":4636,"children":4637},{},[4638,4640,4645,4647,4652,4654,4659],{"type":30,"value":4639},"Versionフィールドを",{"type":24,"tag":46,"props":4641,"children":4643},{"className":4642},[],[4644],{"type":30,"value":3521},{"type":30,"value":4646},"に置くのは意図的です。一番最初に来る数値なので、バイナリの先頭数バイトを見るだけで「これはPIM v2だ」と判定できます。ツール側からの識別が楽になるので、新しく独自フォーマットを作るなら",{"type":24,"tag":46,"props":4648,"children":4650},{"className":4649},[],[4651],{"type":30,"value":2805},{"type":30,"value":4653},"を",{"type":24,"tag":46,"props":4655,"children":4657},{"className":4656},[],[4658],{"type":30,"value":3521},{"type":30,"value":4660},"に置くことを強くお勧めします。",{"type":24,"tag":25,"props":4662,"children":4664},{"id":4663},"readresult-enum-でデシリアライズ失敗を型で表現する",[4665],{"type":30,"value":4666},"ReadResult enum でデシリアライズ失敗を型で表現する",{"type":24,"tag":32,"props":4668,"children":4669},{},[4670,4672,4678,4680,4686],{"type":30,"value":4671},"読み込み結果は単純な",{"type":24,"tag":46,"props":4673,"children":4675},{"className":4674},[],[4676],{"type":30,"value":4677},"Music?",{"type":30,"value":4679},"ではなく、",{"type":24,"tag":46,"props":4681,"children":4683},{"className":4682},[],[4684],{"type":30,"value":4685},"(ReadResult, Music?)",{"type":30,"value":4687},"のタプルで返しています。",{"type":24,"tag":145,"props":4689,"children":4691},{"className":147,"code":4690,"language":149,"meta":8,"style":8},"public enum ReadResult\n{\n    Success,\n    NotFound,\n    FormatError,\n    SoundError,\n}\n",[4692],{"type":24,"tag":46,"props":4693,"children":4694},{"__ignoreMap":8},[4695,4712,4719,4732,4744,4756,4768],{"type":24,"tag":155,"props":4696,"children":4697},{"class":157,"line":18},[4698,4702,4707],{"type":24,"tag":155,"props":4699,"children":4700},{"style":237},[4701],{"type":30,"value":266},{"type":24,"tag":155,"props":4703,"children":4704},{"style":237},[4705],{"type":30,"value":4706}," enum",{"type":24,"tag":155,"props":4708,"children":4709},{"style":167},[4710],{"type":30,"value":4711}," ReadResult\n",{"type":24,"tag":155,"props":4713,"children":4714},{"class":157,"line":189},[4715],{"type":24,"tag":155,"props":4716,"children":4717},{"style":173},[4718],{"type":30,"value":300},{"type":24,"tag":155,"props":4720,"children":4721},{"class":157,"line":223},[4722,4727],{"type":24,"tag":155,"props":4723,"children":4724},{"style":327},[4725],{"type":30,"value":4726},"    Success",{"type":24,"tag":155,"props":4728,"children":4729},{"style":173},[4730],{"type":30,"value":4731},",\n",{"type":24,"tag":155,"props":4733,"children":4734},{"class":157,"line":233},[4735,4740],{"type":24,"tag":155,"props":4736,"children":4737},{"style":327},[4738],{"type":30,"value":4739},"    NotFound",{"type":24,"tag":155,"props":4741,"children":4742},{"style":173},[4743],{"type":30,"value":4731},{"type":24,"tag":155,"props":4745,"children":4746},{"class":157,"line":252},[4747,4752],{"type":24,"tag":155,"props":4748,"children":4749},{"style":327},[4750],{"type":30,"value":4751},"    FormatError",{"type":24,"tag":155,"props":4753,"children":4754},{"style":173},[4755],{"type":30,"value":4731},{"type":24,"tag":155,"props":4757,"children":4758},{"class":157,"line":260},[4759,4764],{"type":24,"tag":155,"props":4760,"children":4761},{"style":327},[4762],{"type":30,"value":4763},"    SoundError",{"type":24,"tag":155,"props":4765,"children":4766},{"style":173},[4767],{"type":30,"value":4731},{"type":24,"tag":155,"props":4769,"children":4770},{"class":157,"line":294},[4771],{"type":24,"tag":155,"props":4772,"children":4773},{"style":173},[4774],{"type":30,"value":694},{"type":24,"tag":32,"props":4776,"children":4777},{},[4778,4780,4786,4788,4793],{"type":30,"value":4779},"最初は",{"type":24,"tag":46,"props":4781,"children":4783},{"className":4782},[],[4784],{"type":30,"value":4785},"try-catch",{"type":30,"value":4787},"で例外を投げる設計だったのですが、 ",{"type":24,"tag":38,"props":4789,"children":4790},{},[4791],{"type":30,"value":4792},"「ユーザーに見せるエラーメッセージを分岐させたい」",{"type":30,"value":4794}," という要件には例外クラスよりenumの方が相性が良いと気付きました。たとえば「フォーマットが壊れている」と「音階の値が不正」では、ユーザーに見せるメッセージが違います。前者は「ファイルが壊れています」で終わりですが、後者は「PICOMのバージョンが古い可能性があります」と案内したい。enumで分岐すればswitch式で網羅チェックも効くので、例外より安全です。",{"type":24,"tag":32,"props":4796,"children":4797},{},[4798,4800,4806],{"type":30,"value":4799},"C#では",{"type":24,"tag":46,"props":4801,"children":4803},{"className":4802},[],[4804],{"type":30,"value":4805},"TryXX",{"type":30,"value":4807},"というメソッドを作ってout引数で結果を返す方法が主流ですが、非同期メソッドでは使えないという問題もあります。PICOMの一部メソッドは非同期で実装されており、できる限りソースコードのパターンを揃えた方が良いという理由もあり、タプル+enumの形で統一しました。",{"type":24,"tag":32,"props":4809,"children":4810},{},[4811],{"type":30,"value":4812},"ただ、この方法にも弱点があり、MusicのNullチェックとReadResultがSuccessかどうかの両方をチェックしないといけないという手間があります。",{"type":24,"tag":25,"props":4814,"children":4816},{"id":4815},"バックアップ機能にも対応複数の曲をまとめて1ファイルに",[4817],{"type":30,"value":4818},"バックアップ機能にも対応！複数の曲をまとめて1ファイルに",{"type":24,"tag":32,"props":4820,"children":4821},{},[4822,4824,4830],{"type":30,"value":4823},"PIMはもう一つ、バックアップ用の",{"type":24,"tag":46,"props":4825,"children":4827},{"className":4826},[],[4828],{"type":30,"value":4829},"BackupDataModel",{"type":30,"value":4831},"を持っています。",{"type":24,"tag":145,"props":4833,"children":4835},{"className":147,"code":4834,"language":149,"meta":8,"style":8},"public byte[] WriteBackup(List\u003CMusic> musics)\n{\n    var backup = new BackupDataModel\n    {\n        Version = 1,\n        CreatedAt = DateTime.UtcNow,\n        Musics = musics.Select(ToDataModel).ToArray(),\n    };\n    return MessagePackSerializer.Serialize(backup);\n}\n",[4836],{"type":24,"tag":46,"props":4837,"children":4838},{"__ignoreMap":8},[4839,4890,4897,4923,4930,4951,4981,5030,5038,5072],{"type":24,"tag":155,"props":4840,"children":4841},{"class":157,"line":18},[4842,4846,4851,4855,4860,4864,4869,4873,4877,4881,4886],{"type":24,"tag":155,"props":4843,"children":4844},{"style":237},[4845],{"type":30,"value":266},{"type":24,"tag":155,"props":4847,"children":4848},{"style":161},[4849],{"type":30,"value":4850}," byte",{"type":24,"tag":155,"props":4852,"children":4853},{"style":173},[4854],{"type":30,"value":3373},{"type":24,"tag":155,"props":4856,"children":4857},{"style":327},[4858],{"type":30,"value":4859}," WriteBackup",{"type":24,"tag":155,"props":4861,"children":4862},{"style":173},[4863],{"type":30,"value":551},{"type":24,"tag":155,"props":4865,"children":4866},{"style":167},[4867],{"type":30,"value":4868},"List",{"type":24,"tag":155,"props":4870,"children":4871},{"style":173},[4872],{"type":30,"value":366},{"type":24,"tag":155,"props":4874,"children":4875},{"style":167},[4876],{"type":30,"value":2109},{"type":24,"tag":155,"props":4878,"children":4879},{"style":173},[4880],{"type":30,"value":1028},{"type":24,"tag":155,"props":4882,"children":4883},{"style":327},[4884],{"type":30,"value":4885}," musics",{"type":24,"tag":155,"props":4887,"children":4888},{"style":173},[4889],{"type":30,"value":453},{"type":24,"tag":155,"props":4891,"children":4892},{"class":157,"line":189},[4893],{"type":24,"tag":155,"props":4894,"children":4895},{"style":173},[4896],{"type":30,"value":300},{"type":24,"tag":155,"props":4898,"children":4899},{"class":157,"line":223},[4900,4905,4910,4914,4918],{"type":24,"tag":155,"props":4901,"children":4902},{"style":237},[4903],{"type":30,"value":4904},"    var",{"type":24,"tag":155,"props":4906,"children":4907},{"style":327},[4908],{"type":30,"value":4909}," backup",{"type":24,"tag":155,"props":4911,"children":4912},{"style":173},[4913],{"type":30,"value":443},{"type":24,"tag":155,"props":4915,"children":4916},{"style":237},[4917],{"type":30,"value":506},{"type":24,"tag":155,"props":4919,"children":4920},{"style":167},[4921],{"type":30,"value":4922}," BackupDataModel\n",{"type":24,"tag":155,"props":4924,"children":4925},{"class":157,"line":233},[4926],{"type":24,"tag":155,"props":4927,"children":4928},{"style":173},[4929],{"type":30,"value":462},{"type":24,"tag":155,"props":4931,"children":4932},{"class":157,"line":252},[4933,4938,4942,4947],{"type":24,"tag":155,"props":4934,"children":4935},{"style":479},[4936],{"type":30,"value":4937},"        Version",{"type":24,"tag":155,"props":4939,"children":4940},{"style":173},[4941],{"type":30,"value":443},{"type":24,"tag":155,"props":4943,"children":4944},{"style":3095},[4945],{"type":30,"value":4946}," 1",{"type":24,"tag":155,"props":4948,"children":4949},{"style":173},[4950],{"type":30,"value":4731},{"type":24,"tag":155,"props":4952,"children":4953},{"class":157,"line":260},[4954,4959,4963,4968,4972,4977],{"type":24,"tag":155,"props":4955,"children":4956},{"style":479},[4957],{"type":30,"value":4958},"        CreatedAt",{"type":24,"tag":155,"props":4960,"children":4961},{"style":173},[4962],{"type":30,"value":443},{"type":24,"tag":155,"props":4964,"children":4965},{"style":479},[4966],{"type":30,"value":4967}," DateTime",{"type":24,"tag":155,"props":4969,"children":4970},{"style":173},[4971],{"type":30,"value":176},{"type":24,"tag":155,"props":4973,"children":4974},{"style":479},[4975],{"type":30,"value":4976},"UtcNow",{"type":24,"tag":155,"props":4978,"children":4979},{"style":173},[4980],{"type":30,"value":4731},{"type":24,"tag":155,"props":4982,"children":4983},{"class":157,"line":294},[4984,4989,4993,4997,5001,5006,5010,5015,5020,5025],{"type":24,"tag":155,"props":4985,"children":4986},{"style":479},[4987],{"type":30,"value":4988},"        Musics",{"type":24,"tag":155,"props":4990,"children":4991},{"style":173},[4992],{"type":30,"value":443},{"type":24,"tag":155,"props":4994,"children":4995},{"style":479},[4996],{"type":30,"value":4885},{"type":24,"tag":155,"props":4998,"children":4999},{"style":173},[5000],{"type":30,"value":176},{"type":24,"tag":155,"props":5002,"children":5003},{"style":327},[5004],{"type":30,"value":5005},"Select",{"type":24,"tag":155,"props":5007,"children":5008},{"style":173},[5009],{"type":30,"value":551},{"type":24,"tag":155,"props":5011,"children":5012},{"style":479},[5013],{"type":30,"value":5014},"ToDataModel",{"type":24,"tag":155,"props":5016,"children":5017},{"style":173},[5018],{"type":30,"value":5019},").",{"type":24,"tag":155,"props":5021,"children":5022},{"style":327},[5023],{"type":30,"value":5024},"ToArray",{"type":24,"tag":155,"props":5026,"children":5027},{"style":173},[5028],{"type":30,"value":5029},"(),\n",{"type":24,"tag":155,"props":5031,"children":5032},{"class":157,"line":303},[5033],{"type":24,"tag":155,"props":5034,"children":5035},{"style":173},[5036],{"type":30,"value":5037},"    };\n",{"type":24,"tag":155,"props":5039,"children":5040},{"class":157,"line":337},[5041,5046,5050,5054,5059,5063,5068],{"type":24,"tag":155,"props":5042,"children":5043},{"style":161},[5044],{"type":30,"value":5045},"    return",{"type":24,"tag":155,"props":5047,"children":5048},{"style":479},[5049],{"type":30,"value":4284},{"type":24,"tag":155,"props":5051,"children":5052},{"style":173},[5053],{"type":30,"value":176},{"type":24,"tag":155,"props":5055,"children":5056},{"style":327},[5057],{"type":30,"value":5058},"Serialize",{"type":24,"tag":155,"props":5060,"children":5061},{"style":173},[5062],{"type":30,"value":551},{"type":24,"tag":155,"props":5064,"children":5065},{"style":479},[5066],{"type":30,"value":5067},"backup",{"type":24,"tag":155,"props":5069,"children":5070},{"style":173},[5071],{"type":30,"value":560},{"type":24,"tag":155,"props":5073,"children":5074},{"class":157,"line":345},[5075],{"type":24,"tag":155,"props":5076,"children":5077},{"style":173},[5078],{"type":30,"value":694},{"type":24,"tag":32,"props":5080,"children":5081},{},[5082,5084,5089,5091,5096,5098,5103],{"type":30,"value":5083},"このバックアップの",{"type":24,"tag":46,"props":5085,"children":5087},{"className":5086},[],[5088],{"type":30,"value":2805},{"type":30,"value":5090},"は曲単体の",{"type":24,"tag":46,"props":5092,"children":5094},{"className":5093},[],[5095],{"type":30,"value":2805},{"type":30,"value":5097},"とは",{"type":24,"tag":38,"props":5099,"children":5100},{},[5101],{"type":30,"value":5102},"別系統",{"type":30,"value":5104},"で管理しています。最初は同じ番号を使っていたのですが、「バックアップ形式だけ変えたい（曲フォーマットは据え置き）」みたいな要件が出て来る可能性を考えて別系統に分けました。",{"type":24,"tag":25,"props":5106,"children":5108},{"id":5107},"追記実際にv3へ上げました",[5109],{"type":30,"value":5110},"追記：実際にv3へ上げました",{"type":24,"tag":32,"props":5112,"children":5113},{},[5114,5116,5122],{"type":30,"value":5115},"この記事を書いた直後に、本文で触れた「いずれv3が必要になったら」が実際に発生しました。PICOMの実装を眺めていて、楽曲IDが「IndexedDBのキー」と「メモリ上の ",{"type":24,"tag":46,"props":5117,"children":5119},{"className":5118},[],[5120],{"type":30,"value":5121},"Music.Id",{"type":30,"value":5123},"」の2箇所に散っており、PIMバイナリ自体はIDを持たない状態だと気付いたのがきっかけです。",{"type":24,"tag":32,"props":5125,"children":5126},{},[5127,5129,5134,5136,5142,5144,5149,5151,5156],{"type":30,"value":5128},"そこで ",{"type":24,"tag":46,"props":5130,"children":5132},{"className":5131},[],[5133],{"type":30,"value":3012},{"type":30,"value":5135}," に ",{"type":24,"tag":46,"props":5137,"children":5139},{"className":5138},[],[5140],{"type":30,"value":5141},"[Key(4)] public int Id { get; set; }",{"type":30,"value":5143}," を追加し、",{"type":24,"tag":46,"props":5145,"children":5147},{"className":5146},[],[5148],{"type":30,"value":2805},{"type":30,"value":5150}," を ",{"type":24,"tag":46,"props":5152,"children":5154},{"className":5153},[],[5155],{"type":30,"value":3352},{"type":30,"value":5157}," に上げました。",{"type":24,"tag":145,"props":5159,"children":5161},{"className":147,"code":5160,"language":149,"meta":8,"style":8},"[MessagePackObject]\npublic class MusicDataModel\n{\n    [Key(0)] public int Version { get; set; } = 3;\n    [Key(1)] public MusicMetadataModel Metadata { get; set; } = new();\n    [Key(2)] public MusicSettingsModel Settings { get; set; } = new();\n    [Key(3)] public TrackDataModel[] Tracks { get; set; } = [];\n    [Key(4)] public int Id { get; set; }\n}\n",[5162],{"type":24,"tag":46,"props":5163,"children":5164},{"__ignoreMap":8},[5165,5180,5195,5202,5274,5345,5416,5487,5547],{"type":24,"tag":155,"props":5166,"children":5167},{"class":157,"line":18},[5168,5172,5176],{"type":24,"tag":155,"props":5169,"children":5170},{"style":173},[5171],{"type":30,"value":3042},{"type":24,"tag":155,"props":5173,"children":5174},{"style":167},[5175],{"type":30,"value":3047},{"type":24,"tag":155,"props":5177,"children":5178},{"style":173},[5179],{"type":30,"value":3052},{"type":24,"tag":155,"props":5181,"children":5182},{"class":157,"line":189},[5183,5187,5191],{"type":24,"tag":155,"props":5184,"children":5185},{"style":237},[5186],{"type":30,"value":266},{"type":24,"tag":155,"props":5188,"children":5189},{"style":237},[5190],{"type":30,"value":276},{"type":24,"tag":155,"props":5192,"children":5193},{"style":167},[5194],{"type":30,"value":3068},{"type":24,"tag":155,"props":5196,"children":5197},{"class":157,"line":223},[5198],{"type":24,"tag":155,"props":5199,"children":5200},{"style":173},[5201],{"type":30,"value":300},{"type":24,"tag":155,"props":5203,"children":5204},{"class":157,"line":233},[5205,5209,5213,5217,5221,5225,5229,5233,5237,5241,5245,5249,5253,5257,5261,5265,5270],{"type":24,"tag":155,"props":5206,"children":5207},{"style":173},[5208],{"type":30,"value":3083},{"type":24,"tag":155,"props":5210,"children":5211},{"style":167},[5212],{"type":30,"value":3088},{"type":24,"tag":155,"props":5214,"children":5215},{"style":173},[5216],{"type":30,"value":551},{"type":24,"tag":155,"props":5218,"children":5219},{"style":3095},[5220],{"type":30,"value":3098},{"type":24,"tag":155,"props":5222,"children":5223},{"style":173},[5224],{"type":30,"value":3595},{"type":24,"tag":155,"props":5226,"children":5227},{"style":237},[5228],{"type":30,"value":3600},{"type":24,"tag":155,"props":5230,"children":5231},{"style":161},[5232],{"type":30,"value":3115},{"type":24,"tag":155,"props":5234,"children":5235},{"style":327},[5236],{"type":30,"value":3120},{"type":24,"tag":155,"props":5238,"children":5239},{"style":173},[5240],{"type":30,"value":1178},{"type":24,"tag":155,"props":5242,"children":5243},{"style":237},[5244],{"type":30,"value":1183},{"type":24,"tag":155,"props":5246,"children":5247},{"style":173},[5248],{"type":30,"value":1188},{"type":24,"tag":155,"props":5250,"children":5251},{"style":237},[5252],{"type":30,"value":1198},{"type":24,"tag":155,"props":5254,"children":5255},{"style":173},[5256],{"type":30,"value":1188},{"type":24,"tag":155,"props":5258,"children":5259},{"style":173},[5260],{"type":30,"value":1207},{"type":24,"tag":155,"props":5262,"children":5263},{"style":173},[5264],{"type":30,"value":443},{"type":24,"tag":155,"props":5266,"children":5267},{"style":3095},[5268],{"type":30,"value":5269}," 3",{"type":24,"tag":155,"props":5271,"children":5272},{"style":173},[5273],{"type":30,"value":186},{"type":24,"tag":155,"props":5275,"children":5276},{"class":157,"line":252},[5277,5281,5285,5289,5293,5297,5301,5305,5309,5313,5317,5321,5325,5329,5333,5337,5341],{"type":24,"tag":155,"props":5278,"children":5279},{"style":173},[5280],{"type":30,"value":3083},{"type":24,"tag":155,"props":5282,"children":5283},{"style":167},[5284],{"type":30,"value":3088},{"type":24,"tag":155,"props":5286,"children":5287},{"style":173},[5288],{"type":30,"value":551},{"type":24,"tag":155,"props":5290,"children":5291},{"style":3095},[5292],{"type":30,"value":3184},{"type":24,"tag":155,"props":5294,"children":5295},{"style":173},[5296],{"type":30,"value":3595},{"type":24,"tag":155,"props":5298,"children":5299},{"style":237},[5300],{"type":30,"value":3600},{"type":24,"tag":155,"props":5302,"children":5303},{"style":167},[5304],{"type":30,"value":3200},{"type":24,"tag":155,"props":5306,"children":5307},{"style":327},[5308],{"type":30,"value":3205},{"type":24,"tag":155,"props":5310,"children":5311},{"style":173},[5312],{"type":30,"value":1178},{"type":24,"tag":155,"props":5314,"children":5315},{"style":237},[5316],{"type":30,"value":1183},{"type":24,"tag":155,"props":5318,"children":5319},{"style":173},[5320],{"type":30,"value":1188},{"type":24,"tag":155,"props":5322,"children":5323},{"style":237},[5324],{"type":30,"value":1198},{"type":24,"tag":155,"props":5326,"children":5327},{"style":173},[5328],{"type":30,"value":1188},{"type":24,"tag":155,"props":5330,"children":5331},{"style":173},[5332],{"type":30,"value":1207},{"type":24,"tag":155,"props":5334,"children":5335},{"style":173},[5336],{"type":30,"value":443},{"type":24,"tag":155,"props":5338,"children":5339},{"style":237},[5340],{"type":30,"value":506},{"type":24,"tag":155,"props":5342,"children":5343},{"style":173},[5344],{"type":30,"value":516},{"type":24,"tag":155,"props":5346,"children":5347},{"class":157,"line":260},[5348,5352,5356,5360,5364,5368,5372,5376,5380,5384,5388,5392,5396,5400,5404,5408,5412],{"type":24,"tag":155,"props":5349,"children":5350},{"style":173},[5351],{"type":30,"value":3083},{"type":24,"tag":155,"props":5353,"children":5354},{"style":167},[5355],{"type":30,"value":3088},{"type":24,"tag":155,"props":5357,"children":5358},{"style":173},[5359],{"type":30,"value":551},{"type":24,"tag":155,"props":5361,"children":5362},{"style":3095},[5363],{"type":30,"value":3268},{"type":24,"tag":155,"props":5365,"children":5366},{"style":173},[5367],{"type":30,"value":3595},{"type":24,"tag":155,"props":5369,"children":5370},{"style":237},[5371],{"type":30,"value":3600},{"type":24,"tag":155,"props":5373,"children":5374},{"style":167},[5375],{"type":30,"value":3284},{"type":24,"tag":155,"props":5377,"children":5378},{"style":327},[5379],{"type":30,"value":3289},{"type":24,"tag":155,"props":5381,"children":5382},{"style":173},[5383],{"type":30,"value":1178},{"type":24,"tag":155,"props":5385,"children":5386},{"style":237},[5387],{"type":30,"value":1183},{"type":24,"tag":155,"props":5389,"children":5390},{"style":173},[5391],{"type":30,"value":1188},{"type":24,"tag":155,"props":5393,"children":5394},{"style":237},[5395],{"type":30,"value":1198},{"type":24,"tag":155,"props":5397,"children":5398},{"style":173},[5399],{"type":30,"value":1188},{"type":24,"tag":155,"props":5401,"children":5402},{"style":173},[5403],{"type":30,"value":1207},{"type":24,"tag":155,"props":5405,"children":5406},{"style":173},[5407],{"type":30,"value":443},{"type":24,"tag":155,"props":5409,"children":5410},{"style":237},[5411],{"type":30,"value":506},{"type":24,"tag":155,"props":5413,"children":5414},{"style":173},[5415],{"type":30,"value":516},{"type":24,"tag":155,"props":5417,"children":5418},{"class":157,"line":294},[5419,5423,5427,5431,5435,5439,5443,5447,5451,5455,5459,5463,5467,5471,5475,5479,5483],{"type":24,"tag":155,"props":5420,"children":5421},{"style":173},[5422],{"type":30,"value":3083},{"type":24,"tag":155,"props":5424,"children":5425},{"style":167},[5426],{"type":30,"value":3088},{"type":24,"tag":155,"props":5428,"children":5429},{"style":173},[5430],{"type":30,"value":551},{"type":24,"tag":155,"props":5432,"children":5433},{"style":3095},[5434],{"type":30,"value":3352},{"type":24,"tag":155,"props":5436,"children":5437},{"style":173},[5438],{"type":30,"value":3595},{"type":24,"tag":155,"props":5440,"children":5441},{"style":237},[5442],{"type":30,"value":3600},{"type":24,"tag":155,"props":5444,"children":5445},{"style":167},[5446],{"type":30,"value":3368},{"type":24,"tag":155,"props":5448,"children":5449},{"style":173},[5450],{"type":30,"value":3373},{"type":24,"tag":155,"props":5452,"children":5453},{"style":327},[5454],{"type":30,"value":3378},{"type":24,"tag":155,"props":5456,"children":5457},{"style":173},[5458],{"type":30,"value":1178},{"type":24,"tag":155,"props":5460,"children":5461},{"style":237},[5462],{"type":30,"value":1183},{"type":24,"tag":155,"props":5464,"children":5465},{"style":173},[5466],{"type":30,"value":1188},{"type":24,"tag":155,"props":5468,"children":5469},{"style":237},[5470],{"type":30,"value":1198},{"type":24,"tag":155,"props":5472,"children":5473},{"style":173},[5474],{"type":30,"value":1188},{"type":24,"tag":155,"props":5476,"children":5477},{"style":173},[5478],{"type":30,"value":1207},{"type":24,"tag":155,"props":5480,"children":5481},{"style":173},[5482],{"type":30,"value":443},{"type":24,"tag":155,"props":5484,"children":5485},{"style":173},[5486],{"type":30,"value":3411},{"type":24,"tag":155,"props":5488,"children":5489},{"class":157,"line":303},[5490,5494,5498,5502,5506,5510,5514,5518,5523,5527,5531,5535,5539,5543],{"type":24,"tag":155,"props":5491,"children":5492},{"style":173},[5493],{"type":30,"value":3083},{"type":24,"tag":155,"props":5495,"children":5496},{"style":167},[5497],{"type":30,"value":3088},{"type":24,"tag":155,"props":5499,"children":5500},{"style":173},[5501],{"type":30,"value":551},{"type":24,"tag":155,"props":5503,"children":5504},{"style":3095},[5505],{"type":30,"value":4035},{"type":24,"tag":155,"props":5507,"children":5508},{"style":173},[5509],{"type":30,"value":3595},{"type":24,"tag":155,"props":5511,"children":5512},{"style":237},[5513],{"type":30,"value":3600},{"type":24,"tag":155,"props":5515,"children":5516},{"style":161},[5517],{"type":30,"value":3115},{"type":24,"tag":155,"props":5519,"children":5520},{"style":327},[5521],{"type":30,"value":5522}," Id",{"type":24,"tag":155,"props":5524,"children":5525},{"style":173},[5526],{"type":30,"value":1178},{"type":24,"tag":155,"props":5528,"children":5529},{"style":237},[5530],{"type":30,"value":1183},{"type":24,"tag":155,"props":5532,"children":5533},{"style":173},[5534],{"type":30,"value":1188},{"type":24,"tag":155,"props":5536,"children":5537},{"style":237},[5538],{"type":30,"value":1198},{"type":24,"tag":155,"props":5540,"children":5541},{"style":173},[5542],{"type":30,"value":1188},{"type":24,"tag":155,"props":5544,"children":5545},{"style":173},[5546],{"type":30,"value":3633},{"type":24,"tag":155,"props":5548,"children":5549},{"class":157,"line":337},[5550],{"type":24,"tag":155,"props":5551,"children":5552},{"style":173},[5553],{"type":30,"value":694},{"type":24,"tag":32,"props":5555,"children":5556},{},[5557,5559,5564,5566,5572,5574,5580,5582,5588],{"type":30,"value":5558},"本文で強調した「",{"type":24,"tag":46,"props":5560,"children":5562},{"className":5561},[],[5563],{"type":30,"value":2813},{"type":30,"value":5565},"番号は追記のみ」のルールに従って、既存の ",{"type":24,"tag":46,"props":5567,"children":5569},{"className":5568},[],[5570],{"type":30,"value":5571},"[Key(0)]〜[Key(3)]",{"type":30,"value":5573}," は一切触らず、末尾に ",{"type":24,"tag":46,"props":5575,"children":5577},{"className":5576},[],[5578],{"type":30,"value":5579},"[Key(4)]",{"type":30,"value":5581}," を足すだけで済みました。そして、読み込み時に ",{"type":24,"tag":46,"props":5583,"children":5585},{"className":5584},[],[5586],{"type":30,"value":5587},"model.Version \u003C 3",{"type":30,"value":5589}," かどうかで旧形式かを判定するだけで済み、バイナリ先頭を見れば版が分かる設計のありがたみを感じました。",{"type":24,"tag":32,"props":5591,"children":5592},{},[5593,5595,5600,5602,5607],{"type":30,"value":5594},"記事本文で「",{"type":24,"tag":46,"props":5596,"children":5598},{"className":5597},[],[5599],{"type":30,"value":2805},{"type":30,"value":5601},"フィールドは最初から入れる。後付けすると必ず苦労する」と書きましたが、",{"type":24,"tag":38,"props":5603,"children":5604},{},[5605],{"type":30,"value":5606},"自分で書いた記事の主張に後から自分で救われた",{"type":30,"value":5608},"形になりました。バージョン管理対応の思想を最初から仕込んでおく価値は、実際に次の版を作るまで実感しづらいのですが、こうやって発揮される瞬間が必ず来ます。",{"type":24,"tag":25,"props":5610,"children":5611},{"id":2679},[5612],{"type":30,"value":2679},{"type":24,"tag":917,"props":5614,"children":5615},{},[5616,5621,5631,5643],{"type":24,"tag":921,"props":5617,"children":5618},{},[5619],{"type":30,"value":5620},"デバッグ性よりサイズ・バイナリとしての扱いやすさ・フォーマット拡張への耐性が大事なら、JSONではなくMessagePackを選ぶ価値がある",{"type":24,"tag":921,"props":5622,"children":5623},{},[5624,5629],{"type":24,"tag":46,"props":5625,"children":5627},{"className":5626},[],[5628],{"type":30,"value":2813},{"type":30,"value":5630},"番号は一度付けたら絶対に変えない、削除もしない",{"type":24,"tag":921,"props":5632,"children":5633},{},[5634,5636,5641],{"type":30,"value":5635},"フォーマットの",{"type":24,"tag":46,"props":5637,"children":5639},{"className":5638},[],[5640],{"type":30,"value":2805},{"type":30,"value":5642},"フィールドは最初から入れる。これを後付けすると必ず苦労する",{"type":24,"tag":921,"props":5644,"children":5645},{},[5646],{"type":30,"value":5647},"デシリアライズ結果はenumで分岐できるようにしておくと、ユーザーへのエラーメッセージ設計が楽になる",{"type":24,"tag":2721,"props":5649,"children":5650},{},[5651],{"type":30,"value":2725},{"title":8,"searchDepth":189,"depth":189,"links":5653},[5654,5655,5659,5660,5662,5663,5664,5665,5666],{"id":27,"depth":189,"text":27},{"id":2818,"depth":189,"text":2821,"children":5656},[5657,5658],{"id":2849,"depth":223,"text":2849},{"id":2889,"depth":223,"text":2892},{"id":2998,"depth":189,"text":3001},{"id":3499,"depth":189,"text":5661},"[Key]番号は追記のみ、絶対に削除しない",{"id":4100,"depth":189,"text":4103},{"id":4663,"depth":189,"text":4666},{"id":4815,"depth":189,"text":4818},{"id":5107,"depth":189,"text":5110},{"id":2679,"depth":189,"text":2679},"content:articles:tech:blazor:messagepack-pim-format.md","articles/tech/blazor/messagepack-pim-format.md","articles/tech/blazor/messagepack-pim-format",{"_path":5671,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":5672,"description":5673,"date":5674,"tags":5675,"rowTypeId":18,"sitemap":5678,"body":5679,"_type":2738,"_id":8769,"_source":2740,"_file":8770,"_stem":8771,"_extension":2743},"/articles/tech/blazor/undo-redo-command-pattern","StackベースのUndo/RedoをC#で実装する｜CompositeCommandで複数操作も一括取り消し","楽譜エディタのUndo/RedoをCommandパターンで実装した実例を紹介します。2本のStackで履歴を管理し、CompositeCommandで複数操作を1手としてまとめ、TrimHistoryで履歴上限を設ける方法を解説します。","2026-04-17",[13,5676,5677,15,17],"デザインパターン","Command",{"loc":5671,"lastmod":5674,"priority":18},{"type":21,"children":5680,"toc":8760},[5681,5685,5690,5703,5733,5739,5744,5756,5762,5767,5988,6039,6044,6477,6502,6508,6534,7429,7434,7454,7488,7494,7499,7512,7800,7826,7890,7911,7917,7944,7949,8480,8492,8497,8688,8693,8697,8756],{"type":24,"tag":25,"props":5682,"children":5683},{"id":27},[5684],{"type":30,"value":27},{"type":24,"tag":32,"props":5686,"children":5687},{},[5688],{"type":30,"value":5689},"楽譜エディタ「PICOM」を作り始めて、Undo/Redoがないと不便だと感じ実装しました。おそらく、多くの編集系UIを持つアプリでは必須機能だと思います。",{"type":24,"tag":32,"props":5691,"children":5692},{},[5693,5695,5701],{"type":30,"value":5694},"そこで、この記事では、PICOMで実装したUndo/Redoの仕組みを紹介します。教科書通りの",{"type":24,"tag":83,"props":5696,"children":5698},{"content":5697},"実行する処理をオブジェクトとしてカプセル化し、履歴管理や取り消しを可能にするデザインパターン",[5699],{"type":30,"value":5700},"Commandパターン",{"type":30,"value":5702},"ですが、実際に使えるものにするには「複数操作を1手としてまとめたい」「履歴が無限に増えるのは困る」といった現実的な課題があります。そのあたりをどう解決したかを具体的に書きます。",{"type":24,"tag":92,"props":5704,"children":5705},{},[5706],{"type":24,"tag":32,"props":5707,"children":5708},{},[5709,5715,5717,5723,5725,5731],{"type":24,"tag":46,"props":5710,"children":5712},{"className":5711},[],[5713],{"type":30,"value":5714},"ICommand",{"type":30,"value":5716},"インターフェースを定義し、",{"type":24,"tag":46,"props":5718,"children":5720},{"className":5719},[],[5721],{"type":30,"value":5722},"UndoRedoManager",{"type":30,"value":5724},"が2本のStackで履歴を管理します。",{"type":24,"tag":46,"props":5726,"children":5728},{"className":5727},[],[5729],{"type":30,"value":5730},"CompositeCommand",{"type":30,"value":5732},"で複数のコマンドを1手にまとめ、ピアノロール上の複数音符を同時に移動するような操作も一発でUndoできます。履歴には上限を設けてメモリリークを防いでいます。",{"type":24,"tag":25,"props":5734,"children":5736},{"id":5735},"undoredoの仕組みを考える",[5737],{"type":30,"value":5738},"Undo/Redoの仕組みを考える",{"type":24,"tag":32,"props":5740,"children":5741},{},[5742],{"type":30,"value":5743},"Undo/Redoを愚直に実装する方法として、操作をするたびに状態を時系列に沿って記憶するという方法が思いつきます。しかし、この方法ではメモリ効率も悪く、状態管理も非常に複雑化します。",{"type":24,"tag":32,"props":5745,"children":5746},{},[5747,5749,5754],{"type":30,"value":5748},"そこで、デザインパターンであるCommandパターンを利用します。Commandパターンでは、状態ではなく ",{"type":24,"tag":38,"props":5750,"children":5751},{},[5752],{"type":30,"value":5753},"操作",{"type":30,"value":5755}," を記憶するため、以前の状態に復元したり、次の状態へ進めたりなどが容易に実現できます。\n続いて、実際の実装を見ていきましょう。",{"type":24,"tag":25,"props":5757,"children":5759},{"id":5758},"icommand最小インターフェースから始める",[5760],{"type":30,"value":5761},"ICommand：最小インターフェースから始める",{"type":24,"tag":32,"props":5763,"children":5764},{},[5765],{"type":30,"value":5766},"Commandパターンの出発点は「実行」と「取り消し」を1ペアで持つインターフェースです。PICOMでは次のように定義しています。",{"type":24,"tag":145,"props":5768,"children":5770},{"className":147,"code":5769,"language":149,"meta":8,"style":8},"public interface ICommand\n{\n    /// \u003Csummary>コマンドを実行する\u003C/summary>\n    void Execute();\n\n    /// \u003Csummary>コマンドを取り消す\u003C/summary>\n    void Undo();\n\n    /// \u003Csummary>コマンドの説明（デバッグ/UI表示用）\u003C/summary>\n    string Description { get; }\n}\n",[5771],{"type":24,"tag":46,"props":5772,"children":5773},{"__ignoreMap":8},[5774,5790,5797,5833,5850,5857,5893,5909,5916,5952,5981],{"type":24,"tag":155,"props":5775,"children":5776},{"class":157,"line":18},[5777,5781,5785],{"type":24,"tag":155,"props":5778,"children":5779},{"style":237},[5780],{"type":30,"value":266},{"type":24,"tag":155,"props":5782,"children":5783},{"style":237},[5784],{"type":30,"value":1515},{"type":24,"tag":155,"props":5786,"children":5787},{"style":167},[5788],{"type":30,"value":5789}," ICommand\n",{"type":24,"tag":155,"props":5791,"children":5792},{"class":157,"line":189},[5793],{"type":24,"tag":155,"props":5794,"children":5795},{"style":173},[5796],{"type":30,"value":300},{"type":24,"tag":155,"props":5798,"children":5799},{"class":157,"line":223},[5800,5804,5808,5812,5816,5821,5825,5829],{"type":24,"tag":155,"props":5801,"children":5802},{"style":1411},[5803],{"type":30,"value":1547},{"type":24,"tag":155,"props":5805,"children":5806},{"style":173},[5807],{"type":30,"value":366},{"type":24,"tag":155,"props":5809,"children":5810},{"style":161},[5811],{"type":30,"value":1556},{"type":24,"tag":155,"props":5813,"children":5814},{"style":173},[5815],{"type":30,"value":1028},{"type":24,"tag":155,"props":5817,"children":5818},{"style":1411},[5819],{"type":30,"value":5820},"コマンドを実行する",{"type":24,"tag":155,"props":5822,"children":5823},{"style":173},[5824],{"type":30,"value":1570},{"type":24,"tag":155,"props":5826,"children":5827},{"style":161},[5828],{"type":30,"value":1556},{"type":24,"tag":155,"props":5830,"children":5831},{"style":173},[5832],{"type":30,"value":1067},{"type":24,"tag":155,"props":5834,"children":5835},{"class":157,"line":233},[5836,5841,5846],{"type":24,"tag":155,"props":5837,"children":5838},{"style":161},[5839],{"type":30,"value":5840},"    void",{"type":24,"tag":155,"props":5842,"children":5843},{"style":327},[5844],{"type":30,"value":5845}," Execute",{"type":24,"tag":155,"props":5847,"children":5848},{"style":173},[5849],{"type":30,"value":516},{"type":24,"tag":155,"props":5851,"children":5852},{"class":157,"line":252},[5853],{"type":24,"tag":155,"props":5854,"children":5855},{"emptyLinePlaceholder":227},[5856],{"type":30,"value":230},{"type":24,"tag":155,"props":5858,"children":5859},{"class":157,"line":260},[5860,5864,5868,5872,5876,5881,5885,5889],{"type":24,"tag":155,"props":5861,"children":5862},{"style":1411},[5863],{"type":30,"value":1547},{"type":24,"tag":155,"props":5865,"children":5866},{"style":173},[5867],{"type":30,"value":366},{"type":24,"tag":155,"props":5869,"children":5870},{"style":161},[5871],{"type":30,"value":1556},{"type":24,"tag":155,"props":5873,"children":5874},{"style":173},[5875],{"type":30,"value":1028},{"type":24,"tag":155,"props":5877,"children":5878},{"style":1411},[5879],{"type":30,"value":5880},"コマンドを取り消す",{"type":24,"tag":155,"props":5882,"children":5883},{"style":173},[5884],{"type":30,"value":1570},{"type":24,"tag":155,"props":5886,"children":5887},{"style":161},[5888],{"type":30,"value":1556},{"type":24,"tag":155,"props":5890,"children":5891},{"style":173},[5892],{"type":30,"value":1067},{"type":24,"tag":155,"props":5894,"children":5895},{"class":157,"line":294},[5896,5900,5905],{"type":24,"tag":155,"props":5897,"children":5898},{"style":161},[5899],{"type":30,"value":5840},{"type":24,"tag":155,"props":5901,"children":5902},{"style":327},[5903],{"type":30,"value":5904}," Undo",{"type":24,"tag":155,"props":5906,"children":5907},{"style":173},[5908],{"type":30,"value":516},{"type":24,"tag":155,"props":5910,"children":5911},{"class":157,"line":303},[5912],{"type":24,"tag":155,"props":5913,"children":5914},{"emptyLinePlaceholder":227},[5915],{"type":30,"value":230},{"type":24,"tag":155,"props":5917,"children":5918},{"class":157,"line":337},[5919,5923,5927,5931,5935,5940,5944,5948],{"type":24,"tag":155,"props":5920,"children":5921},{"style":1411},[5922],{"type":30,"value":1547},{"type":24,"tag":155,"props":5924,"children":5925},{"style":173},[5926],{"type":30,"value":366},{"type":24,"tag":155,"props":5928,"children":5929},{"style":161},[5930],{"type":30,"value":1556},{"type":24,"tag":155,"props":5932,"children":5933},{"style":173},[5934],{"type":30,"value":1028},{"type":24,"tag":155,"props":5936,"children":5937},{"style":1411},[5938],{"type":30,"value":5939},"コマンドの説明（デバッグ/UI表示用）",{"type":24,"tag":155,"props":5941,"children":5942},{"style":173},[5943],{"type":30,"value":1570},{"type":24,"tag":155,"props":5945,"children":5946},{"style":161},[5947],{"type":30,"value":1556},{"type":24,"tag":155,"props":5949,"children":5950},{"style":173},[5951],{"type":30,"value":1067},{"type":24,"tag":155,"props":5953,"children":5954},{"class":157,"line":345},[5955,5960,5965,5969,5973,5977],{"type":24,"tag":155,"props":5956,"children":5957},{"style":161},[5958],{"type":30,"value":5959},"    string",{"type":24,"tag":155,"props":5961,"children":5962},{"style":327},[5963],{"type":30,"value":5964}," Description",{"type":24,"tag":155,"props":5966,"children":5967},{"style":173},[5968],{"type":30,"value":1178},{"type":24,"tag":155,"props":5970,"children":5971},{"style":237},[5972],{"type":30,"value":1183},{"type":24,"tag":155,"props":5974,"children":5975},{"style":173},[5976],{"type":30,"value":1188},{"type":24,"tag":155,"props":5978,"children":5979},{"style":173},[5980],{"type":30,"value":3633},{"type":24,"tag":155,"props":5982,"children":5983},{"class":157,"line":456},[5984],{"type":24,"tag":155,"props":5985,"children":5986},{"style":173},[5987],{"type":30,"value":694},{"type":24,"tag":32,"props":5989,"children":5990},{},[5991,5993,5999,6001,6007,6009,6014,6016,6022,6024,6030,6032,6037],{"type":30,"value":5992},"ポイントは",{"type":24,"tag":46,"props":5994,"children":5996},{"className":5995},[],[5997],{"type":30,"value":5998},"Description",{"type":30,"value":6000},"を入れていることです。履歴一覧を出したり、デバッグで",{"type":24,"tag":46,"props":6002,"children":6004},{"className":6003},[],[6005],{"type":30,"value":6006},"Console.WriteLine",{"type":30,"value":6008},"する時に、どのコマンドが何をしたのかを文字列で追えると",{"type":24,"tag":38,"props":6010,"children":6011},{},[6012],{"type":30,"value":6013},"デバッグの効率が雲泥の差",{"type":30,"value":6015},"です。.NET標準の",{"type":24,"tag":46,"props":6017,"children":6019},{"className":6018},[],[6020],{"type":30,"value":6021},"System.Windows.Input.ICommand",{"type":30,"value":6023},"とは別物なので、名前が被る場合は名前空間を明示するかリネームしてください。PICOMはBlazor WASMで実装しているため、",{"type":24,"tag":46,"props":6025,"children":6027},{"className":6026},[],[6028],{"type":30,"value":6029},"System.Windows",{"type":30,"value":6031},"の",{"type":24,"tag":46,"props":6033,"children":6035},{"className":6034},[],[6036],{"type":30,"value":5714},{"type":30,"value":6038},"と衝突することはなかったです。",{"type":24,"tag":32,"props":6040,"children":6041},{},[6042],{"type":30,"value":6043},"具体的なコマンドの実装例として、音符追加コマンドを載せておきます。",{"type":24,"tag":145,"props":6045,"children":6047},{"className":147,"code":6046,"language":149,"meta":8,"style":8},"public class AddNoteCommand : ICommand\n{\n    private readonly Track _track;\n    private readonly int _position128Note;\n    private readonly SoundComponentBase _component;\n\n    public string Description => $\"ノート追加 at {_position128Note}\";\n\n    public AddNoteCommand(Track track, int position128Note, SoundComponentBase component)\n    {\n        _track = track;\n        _position128Note = position128Note;\n        _component = component;\n    }\n\n    public void Execute() => _track.AddAt(_position128Note, _component);\n    public void Undo() => _track.RemoveAt(_position128Note, _component);\n}\n",[6048],{"type":24,"tag":46,"props":6049,"children":6050},{"__ignoreMap":8},[6051,6075,6082,6108,6132,6157,6164,6214,6221,6276,6283,6303,6323,6343,6350,6357,6414,6470],{"type":24,"tag":155,"props":6052,"children":6053},{"class":157,"line":18},[6054,6058,6062,6067,6071],{"type":24,"tag":155,"props":6055,"children":6056},{"style":237},[6057],{"type":30,"value":266},{"type":24,"tag":155,"props":6059,"children":6060},{"style":237},[6061],{"type":30,"value":276},{"type":24,"tag":155,"props":6063,"children":6064},{"style":167},[6065],{"type":30,"value":6066}," AddNoteCommand",{"type":24,"tag":155,"props":6068,"children":6069},{"style":173},[6070],{"type":30,"value":286},{"type":24,"tag":155,"props":6072,"children":6073},{"style":167},[6074],{"type":30,"value":5789},{"type":24,"tag":155,"props":6076,"children":6077},{"class":157,"line":189},[6078],{"type":24,"tag":155,"props":6079,"children":6080},{"style":173},[6081],{"type":30,"value":300},{"type":24,"tag":155,"props":6083,"children":6084},{"class":157,"line":223},[6085,6089,6094,6099,6104],{"type":24,"tag":155,"props":6086,"children":6087},{"style":237},[6088],{"type":30,"value":752},{"type":24,"tag":155,"props":6090,"children":6091},{"style":237},[6092],{"type":30,"value":6093}," readonly",{"type":24,"tag":155,"props":6095,"children":6096},{"style":167},[6097],{"type":30,"value":6098}," Track",{"type":24,"tag":155,"props":6100,"children":6101},{"style":327},[6102],{"type":30,"value":6103}," _track",{"type":24,"tag":155,"props":6105,"children":6106},{"style":173},[6107],{"type":30,"value":186},{"type":24,"tag":155,"props":6109,"children":6110},{"class":157,"line":233},[6111,6115,6119,6123,6128],{"type":24,"tag":155,"props":6112,"children":6113},{"style":237},[6114],{"type":30,"value":752},{"type":24,"tag":155,"props":6116,"children":6117},{"style":237},[6118],{"type":30,"value":6093},{"type":24,"tag":155,"props":6120,"children":6121},{"style":161},[6122],{"type":30,"value":3115},{"type":24,"tag":155,"props":6124,"children":6125},{"style":327},[6126],{"type":30,"value":6127}," _position128Note",{"type":24,"tag":155,"props":6129,"children":6130},{"style":173},[6131],{"type":30,"value":186},{"type":24,"tag":155,"props":6133,"children":6134},{"class":157,"line":252},[6135,6139,6143,6148,6153],{"type":24,"tag":155,"props":6136,"children":6137},{"style":237},[6138],{"type":30,"value":752},{"type":24,"tag":155,"props":6140,"children":6141},{"style":237},[6142],{"type":30,"value":6093},{"type":24,"tag":155,"props":6144,"children":6145},{"style":167},[6146],{"type":30,"value":6147}," SoundComponentBase",{"type":24,"tag":155,"props":6149,"children":6150},{"style":327},[6151],{"type":30,"value":6152}," _component",{"type":24,"tag":155,"props":6154,"children":6155},{"style":173},[6156],{"type":30,"value":186},{"type":24,"tag":155,"props":6158,"children":6159},{"class":157,"line":260},[6160],{"type":24,"tag":155,"props":6161,"children":6162},{"emptyLinePlaceholder":227},[6163],{"type":30,"value":230},{"type":24,"tag":155,"props":6165,"children":6166},{"class":157,"line":294},[6167,6171,6175,6179,6183,6188,6193,6197,6202,6206,6210],{"type":24,"tag":155,"props":6168,"children":6169},{"style":237},[6170],{"type":30,"value":309},{"type":24,"tag":155,"props":6172,"children":6173},{"style":161},[6174],{"type":30,"value":429},{"type":24,"tag":155,"props":6176,"children":6177},{"style":327},[6178],{"type":30,"value":5964},{"type":24,"tag":155,"props":6180,"children":6181},{"style":237},[6182],{"type":30,"value":811},{"type":24,"tag":155,"props":6184,"children":6185},{"style":768},[6186],{"type":30,"value":6187}," $\"",{"type":24,"tag":155,"props":6189,"children":6190},{"style":2239},[6191],{"type":30,"value":6192},"ノート追加 at ",{"type":24,"tag":155,"props":6194,"children":6195},{"style":173},[6196],{"type":30,"value":4350},{"type":24,"tag":155,"props":6198,"children":6199},{"style":2239},[6200],{"type":30,"value":6201},"_position128Note",{"type":24,"tag":155,"props":6203,"children":6204},{"style":173},[6205],{"type":30,"value":4368},{"type":24,"tag":155,"props":6207,"children":6208},{"style":768},[6209],{"type":30,"value":2246},{"type":24,"tag":155,"props":6211,"children":6212},{"style":173},[6213],{"type":30,"value":186},{"type":24,"tag":155,"props":6215,"children":6216},{"class":157,"line":303},[6217],{"type":24,"tag":155,"props":6218,"children":6219},{"emptyLinePlaceholder":227},[6220],{"type":30,"value":230},{"type":24,"tag":155,"props":6222,"children":6223},{"class":157,"line":337},[6224,6228,6232,6236,6241,6246,6250,6254,6259,6263,6267,6272],{"type":24,"tag":155,"props":6225,"children":6226},{"style":237},[6227],{"type":30,"value":309},{"type":24,"tag":155,"props":6229,"children":6230},{"style":327},[6231],{"type":30,"value":6066},{"type":24,"tag":155,"props":6233,"children":6234},{"style":173},[6235],{"type":30,"value":551},{"type":24,"tag":155,"props":6237,"children":6238},{"style":167},[6239],{"type":30,"value":6240},"Track",{"type":24,"tag":155,"props":6242,"children":6243},{"style":327},[6244],{"type":30,"value":6245}," track",{"type":24,"tag":155,"props":6247,"children":6248},{"style":173},[6249],{"type":30,"value":396},{"type":24,"tag":155,"props":6251,"children":6252},{"style":161},[6253],{"type":30,"value":3115},{"type":24,"tag":155,"props":6255,"children":6256},{"style":327},[6257],{"type":30,"value":6258}," position128Note",{"type":24,"tag":155,"props":6260,"children":6261},{"style":173},[6262],{"type":30,"value":396},{"type":24,"tag":155,"props":6264,"children":6265},{"style":167},[6266],{"type":30,"value":6147},{"type":24,"tag":155,"props":6268,"children":6269},{"style":327},[6270],{"type":30,"value":6271}," component",{"type":24,"tag":155,"props":6273,"children":6274},{"style":173},[6275],{"type":30,"value":453},{"type":24,"tag":155,"props":6277,"children":6278},{"class":157,"line":345},[6279],{"type":24,"tag":155,"props":6280,"children":6281},{"style":173},[6282],{"type":30,"value":462},{"type":24,"tag":155,"props":6284,"children":6285},{"class":157,"line":456},[6286,6291,6295,6299],{"type":24,"tag":155,"props":6287,"children":6288},{"style":479},[6289],{"type":30,"value":6290},"        _track",{"type":24,"tag":155,"props":6292,"children":6293},{"style":173},[6294],{"type":30,"value":443},{"type":24,"tag":155,"props":6296,"children":6297},{"style":479},[6298],{"type":30,"value":6245},{"type":24,"tag":155,"props":6300,"children":6301},{"style":173},[6302],{"type":30,"value":186},{"type":24,"tag":155,"props":6304,"children":6305},{"class":157,"line":465},[6306,6311,6315,6319],{"type":24,"tag":155,"props":6307,"children":6308},{"style":479},[6309],{"type":30,"value":6310},"        _position128Note",{"type":24,"tag":155,"props":6312,"children":6313},{"style":173},[6314],{"type":30,"value":443},{"type":24,"tag":155,"props":6316,"children":6317},{"style":479},[6318],{"type":30,"value":6258},{"type":24,"tag":155,"props":6320,"children":6321},{"style":173},[6322],{"type":30,"value":186},{"type":24,"tag":155,"props":6324,"children":6325},{"class":157,"line":519},[6326,6331,6335,6339],{"type":24,"tag":155,"props":6327,"children":6328},{"style":479},[6329],{"type":30,"value":6330},"        _component",{"type":24,"tag":155,"props":6332,"children":6333},{"style":173},[6334],{"type":30,"value":443},{"type":24,"tag":155,"props":6336,"children":6337},{"style":479},[6338],{"type":30,"value":6271},{"type":24,"tag":155,"props":6340,"children":6341},{"style":173},[6342],{"type":30,"value":186},{"type":24,"tag":155,"props":6344,"children":6345},{"class":157,"line":540},[6346],{"type":24,"tag":155,"props":6347,"children":6348},{"style":173},[6349],{"type":30,"value":569},{"type":24,"tag":155,"props":6351,"children":6352},{"class":157,"line":563},[6353],{"type":24,"tag":155,"props":6354,"children":6355},{"emptyLinePlaceholder":227},[6356],{"type":30,"value":230},{"type":24,"tag":155,"props":6358,"children":6359},{"class":157,"line":572},[6360,6364,6368,6372,6377,6381,6385,6389,6394,6398,6402,6406,6410],{"type":24,"tag":155,"props":6361,"children":6362},{"style":237},[6363],{"type":30,"value":309},{"type":24,"tag":155,"props":6365,"children":6366},{"style":161},[6367],{"type":30,"value":356},{"type":24,"tag":155,"props":6369,"children":6370},{"style":327},[6371],{"type":30,"value":5845},{"type":24,"tag":155,"props":6373,"children":6374},{"style":173},[6375],{"type":30,"value":6376},"()",{"type":24,"tag":155,"props":6378,"children":6379},{"style":237},[6380],{"type":30,"value":811},{"type":24,"tag":155,"props":6382,"children":6383},{"style":479},[6384],{"type":30,"value":6103},{"type":24,"tag":155,"props":6386,"children":6387},{"style":173},[6388],{"type":30,"value":176},{"type":24,"tag":155,"props":6390,"children":6391},{"style":327},[6392],{"type":30,"value":6393},"AddAt",{"type":24,"tag":155,"props":6395,"children":6396},{"style":173},[6397],{"type":30,"value":551},{"type":24,"tag":155,"props":6399,"children":6400},{"style":479},[6401],{"type":30,"value":6201},{"type":24,"tag":155,"props":6403,"children":6404},{"style":173},[6405],{"type":30,"value":396},{"type":24,"tag":155,"props":6407,"children":6408},{"style":479},[6409],{"type":30,"value":6152},{"type":24,"tag":155,"props":6411,"children":6412},{"style":173},[6413],{"type":30,"value":560},{"type":24,"tag":155,"props":6415,"children":6416},{"class":157,"line":580},[6417,6421,6425,6429,6433,6437,6441,6445,6450,6454,6458,6462,6466],{"type":24,"tag":155,"props":6418,"children":6419},{"style":237},[6420],{"type":30,"value":309},{"type":24,"tag":155,"props":6422,"children":6423},{"style":161},[6424],{"type":30,"value":356},{"type":24,"tag":155,"props":6426,"children":6427},{"style":327},[6428],{"type":30,"value":5904},{"type":24,"tag":155,"props":6430,"children":6431},{"style":173},[6432],{"type":30,"value":6376},{"type":24,"tag":155,"props":6434,"children":6435},{"style":237},[6436],{"type":30,"value":811},{"type":24,"tag":155,"props":6438,"children":6439},{"style":479},[6440],{"type":30,"value":6103},{"type":24,"tag":155,"props":6442,"children":6443},{"style":173},[6444],{"type":30,"value":176},{"type":24,"tag":155,"props":6446,"children":6447},{"style":327},[6448],{"type":30,"value":6449},"RemoveAt",{"type":24,"tag":155,"props":6451,"children":6452},{"style":173},[6453],{"type":30,"value":551},{"type":24,"tag":155,"props":6455,"children":6456},{"style":479},[6457],{"type":30,"value":6201},{"type":24,"tag":155,"props":6459,"children":6460},{"style":173},[6461],{"type":30,"value":396},{"type":24,"tag":155,"props":6463,"children":6464},{"style":479},[6465],{"type":30,"value":6152},{"type":24,"tag":155,"props":6467,"children":6468},{"style":173},[6469],{"type":30,"value":560},{"type":24,"tag":155,"props":6471,"children":6472},{"class":157,"line":614},[6473],{"type":24,"tag":155,"props":6474,"children":6475},{"style":173},[6476],{"type":30,"value":694},{"type":24,"tag":32,"props":6478,"children":6479},{},[6480,6486,6487,6493,6495,6500],{"type":24,"tag":46,"props":6481,"children":6483},{"className":6482},[],[6484],{"type":30,"value":6485},"Execute",{"type":30,"value":73},{"type":24,"tag":46,"props":6488,"children":6490},{"className":6489},[],[6491],{"type":30,"value":6492},"Undo",{"type":30,"value":6494},"が対称になっているのがCommandパターンの気持ちよさです。",{"type":24,"tag":46,"props":6496,"children":6498},{"className":6497},[],[6499],{"type":30,"value":6492},{"type":30,"value":6501},"で失敗するような操作は原則として作らない、というのが運用上の鉄則です。",{"type":24,"tag":25,"props":6503,"children":6505},{"id":6504},"undoredomanager2本のstackで履歴を管理",[6506],{"type":30,"value":6507},"UndoRedoManager：2本のStackで履歴を管理",{"type":24,"tag":32,"props":6509,"children":6510},{},[6511,6513,6518,6519,6525,6526,6532],{"type":30,"value":6512},"履歴の管理本体は",{"type":24,"tag":46,"props":6514,"children":6516},{"className":6515},[],[6517],{"type":30,"value":5722},{"type":30,"value":2846},{"type":24,"tag":46,"props":6520,"children":6522},{"className":6521},[],[6523],{"type":30,"value":6524},"_undoStack",{"type":30,"value":73},{"type":24,"tag":46,"props":6527,"children":6529},{"className":6528},[],[6530],{"type":30,"value":6531},"_redoStack",{"type":30,"value":6533},"の2本で押したり引いたりします。",{"type":24,"tag":145,"props":6535,"children":6537},{"className":147,"code":6536,"language":149,"meta":8,"style":8},"public class UndoRedoManager\n{\n    private readonly Stack\u003CICommand> _undoStack = new();\n    private readonly Stack\u003CICommand> _redoStack = new();\n    private readonly int _maxHistorySize;\n\n    public event Action? StateChanged;\n\n    public UndoRedoManager(int maxHistorySize = 100)\n    {\n        _maxHistorySize = maxHistorySize;\n    }\n\n    public bool CanUndo => _undoStack.Count > 0;\n    public bool CanRedo => _redoStack.Count > 0;\n\n    public void Execute(ICommand command)\n    {\n        command.Execute();\n        _undoStack.Push(command);\n        _redoStack.Clear();\n        TrimHistory();\n        StateChanged?.Invoke();\n    }\n\n    public void Undo()\n    {\n        if (!CanUndo) return;\n        var command = _undoStack.Pop();\n        command.Undo();\n        _redoStack.Push(command);\n        StateChanged?.Invoke();\n    }\n\n    public void Redo()\n    {\n        if (!CanRedo) return;\n        var command = _redoStack.Pop();\n        command.Execute();\n        _undoStack.Push(command);\n        StateChanged?.Invoke();\n    }\n}\n",[6538],{"type":24,"tag":46,"props":6539,"children":6540},{"__ignoreMap":8},[6541,6557,6564,6609,6653,6677,6684,6713,6720,6759,6766,6786,6793,6800,6847,6891,6898,6930,6937,6957,6987,7008,7021,7046,7054,7062,7083,7091,7125,7159,7179,7207,7231,7239,7247,7268,7276,7309,7341,7361,7389,7413,7421],{"type":24,"tag":155,"props":6542,"children":6543},{"class":157,"line":18},[6544,6548,6552],{"type":24,"tag":155,"props":6545,"children":6546},{"style":237},[6547],{"type":30,"value":266},{"type":24,"tag":155,"props":6549,"children":6550},{"style":237},[6551],{"type":30,"value":276},{"type":24,"tag":155,"props":6553,"children":6554},{"style":167},[6555],{"type":30,"value":6556}," UndoRedoManager\n",{"type":24,"tag":155,"props":6558,"children":6559},{"class":157,"line":189},[6560],{"type":24,"tag":155,"props":6561,"children":6562},{"style":173},[6563],{"type":30,"value":300},{"type":24,"tag":155,"props":6565,"children":6566},{"class":157,"line":223},[6567,6571,6575,6580,6584,6588,6592,6597,6601,6605],{"type":24,"tag":155,"props":6568,"children":6569},{"style":237},[6570],{"type":30,"value":752},{"type":24,"tag":155,"props":6572,"children":6573},{"style":237},[6574],{"type":30,"value":6093},{"type":24,"tag":155,"props":6576,"children":6577},{"style":167},[6578],{"type":30,"value":6579}," Stack",{"type":24,"tag":155,"props":6581,"children":6582},{"style":173},[6583],{"type":30,"value":366},{"type":24,"tag":155,"props":6585,"children":6586},{"style":167},[6587],{"type":30,"value":5714},{"type":24,"tag":155,"props":6589,"children":6590},{"style":173},[6591],{"type":30,"value":1028},{"type":24,"tag":155,"props":6593,"children":6594},{"style":327},[6595],{"type":30,"value":6596}," _undoStack",{"type":24,"tag":155,"props":6598,"children":6599},{"style":173},[6600],{"type":30,"value":443},{"type":24,"tag":155,"props":6602,"children":6603},{"style":237},[6604],{"type":30,"value":506},{"type":24,"tag":155,"props":6606,"children":6607},{"style":173},[6608],{"type":30,"value":516},{"type":24,"tag":155,"props":6610,"children":6611},{"class":157,"line":233},[6612,6616,6620,6624,6628,6632,6636,6641,6645,6649],{"type":24,"tag":155,"props":6613,"children":6614},{"style":237},[6615],{"type":30,"value":752},{"type":24,"tag":155,"props":6617,"children":6618},{"style":237},[6619],{"type":30,"value":6093},{"type":24,"tag":155,"props":6621,"children":6622},{"style":167},[6623],{"type":30,"value":6579},{"type":24,"tag":155,"props":6625,"children":6626},{"style":173},[6627],{"type":30,"value":366},{"type":24,"tag":155,"props":6629,"children":6630},{"style":167},[6631],{"type":30,"value":5714},{"type":24,"tag":155,"props":6633,"children":6634},{"style":173},[6635],{"type":30,"value":1028},{"type":24,"tag":155,"props":6637,"children":6638},{"style":327},[6639],{"type":30,"value":6640}," _redoStack",{"type":24,"tag":155,"props":6642,"children":6643},{"style":173},[6644],{"type":30,"value":443},{"type":24,"tag":155,"props":6646,"children":6647},{"style":237},[6648],{"type":30,"value":506},{"type":24,"tag":155,"props":6650,"children":6651},{"style":173},[6652],{"type":30,"value":516},{"type":24,"tag":155,"props":6654,"children":6655},{"class":157,"line":252},[6656,6660,6664,6668,6673],{"type":24,"tag":155,"props":6657,"children":6658},{"style":237},[6659],{"type":30,"value":752},{"type":24,"tag":155,"props":6661,"children":6662},{"style":237},[6663],{"type":30,"value":6093},{"type":24,"tag":155,"props":6665,"children":6666},{"style":161},[6667],{"type":30,"value":3115},{"type":24,"tag":155,"props":6669,"children":6670},{"style":327},[6671],{"type":30,"value":6672}," _maxHistorySize",{"type":24,"tag":155,"props":6674,"children":6675},{"style":173},[6676],{"type":30,"value":186},{"type":24,"tag":155,"props":6678,"children":6679},{"class":157,"line":260},[6680],{"type":24,"tag":155,"props":6681,"children":6682},{"emptyLinePlaceholder":227},[6683],{"type":30,"value":230},{"type":24,"tag":155,"props":6685,"children":6686},{"class":157,"line":294},[6687,6691,6695,6700,6704,6709],{"type":24,"tag":155,"props":6688,"children":6689},{"style":237},[6690],{"type":30,"value":309},{"type":24,"tag":155,"props":6692,"children":6693},{"style":237},[6694],{"type":30,"value":314},{"type":24,"tag":155,"props":6696,"children":6697},{"style":167},[6698],{"type":30,"value":6699}," Action",{"type":24,"tag":155,"props":6701,"children":6702},{"style":173},[6703],{"type":30,"value":324},{"type":24,"tag":155,"props":6705,"children":6706},{"style":327},[6707],{"type":30,"value":6708}," StateChanged",{"type":24,"tag":155,"props":6710,"children":6711},{"style":173},[6712],{"type":30,"value":186},{"type":24,"tag":155,"props":6714,"children":6715},{"class":157,"line":303},[6716],{"type":24,"tag":155,"props":6717,"children":6718},{"emptyLinePlaceholder":227},[6719],{"type":30,"value":230},{"type":24,"tag":155,"props":6721,"children":6722},{"class":157,"line":337},[6723,6727,6732,6736,6741,6746,6750,6755],{"type":24,"tag":155,"props":6724,"children":6725},{"style":237},[6726],{"type":30,"value":309},{"type":24,"tag":155,"props":6728,"children":6729},{"style":327},[6730],{"type":30,"value":6731}," UndoRedoManager",{"type":24,"tag":155,"props":6733,"children":6734},{"style":173},[6735],{"type":30,"value":551},{"type":24,"tag":155,"props":6737,"children":6738},{"style":161},[6739],{"type":30,"value":6740},"int",{"type":24,"tag":155,"props":6742,"children":6743},{"style":327},[6744],{"type":30,"value":6745}," maxHistorySize",{"type":24,"tag":155,"props":6747,"children":6748},{"style":173},[6749],{"type":30,"value":443},{"type":24,"tag":155,"props":6751,"children":6752},{"style":3095},[6753],{"type":30,"value":6754}," 100",{"type":24,"tag":155,"props":6756,"children":6757},{"style":173},[6758],{"type":30,"value":453},{"type":24,"tag":155,"props":6760,"children":6761},{"class":157,"line":345},[6762],{"type":24,"tag":155,"props":6763,"children":6764},{"style":173},[6765],{"type":30,"value":462},{"type":24,"tag":155,"props":6767,"children":6768},{"class":157,"line":456},[6769,6774,6778,6782],{"type":24,"tag":155,"props":6770,"children":6771},{"style":479},[6772],{"type":30,"value":6773},"        _maxHistorySize",{"type":24,"tag":155,"props":6775,"children":6776},{"style":173},[6777],{"type":30,"value":443},{"type":24,"tag":155,"props":6779,"children":6780},{"style":479},[6781],{"type":30,"value":6745},{"type":24,"tag":155,"props":6783,"children":6784},{"style":173},[6785],{"type":30,"value":186},{"type":24,"tag":155,"props":6787,"children":6788},{"class":157,"line":465},[6789],{"type":24,"tag":155,"props":6790,"children":6791},{"style":173},[6792],{"type":30,"value":569},{"type":24,"tag":155,"props":6794,"children":6795},{"class":157,"line":519},[6796],{"type":24,"tag":155,"props":6797,"children":6798},{"emptyLinePlaceholder":227},[6799],{"type":30,"value":230},{"type":24,"tag":155,"props":6801,"children":6802},{"class":157,"line":540},[6803,6807,6811,6816,6820,6824,6828,6833,6838,6843],{"type":24,"tag":155,"props":6804,"children":6805},{"style":237},[6806],{"type":30,"value":309},{"type":24,"tag":155,"props":6808,"children":6809},{"style":161},[6810],{"type":30,"value":1117},{"type":24,"tag":155,"props":6812,"children":6813},{"style":327},[6814],{"type":30,"value":6815}," CanUndo",{"type":24,"tag":155,"props":6817,"children":6818},{"style":237},[6819],{"type":30,"value":811},{"type":24,"tag":155,"props":6821,"children":6822},{"style":479},[6823],{"type":30,"value":6596},{"type":24,"tag":155,"props":6825,"children":6826},{"style":173},[6827],{"type":30,"value":176},{"type":24,"tag":155,"props":6829,"children":6830},{"style":479},[6831],{"type":30,"value":6832},"Count",{"type":24,"tag":155,"props":6834,"children":6835},{"style":173},[6836],{"type":30,"value":6837}," >",{"type":24,"tag":155,"props":6839,"children":6840},{"style":3095},[6841],{"type":30,"value":6842}," 0",{"type":24,"tag":155,"props":6844,"children":6845},{"style":173},[6846],{"type":30,"value":186},{"type":24,"tag":155,"props":6848,"children":6849},{"class":157,"line":563},[6850,6854,6858,6863,6867,6871,6875,6879,6883,6887],{"type":24,"tag":155,"props":6851,"children":6852},{"style":237},[6853],{"type":30,"value":309},{"type":24,"tag":155,"props":6855,"children":6856},{"style":161},[6857],{"type":30,"value":1117},{"type":24,"tag":155,"props":6859,"children":6860},{"style":327},[6861],{"type":30,"value":6862}," CanRedo",{"type":24,"tag":155,"props":6864,"children":6865},{"style":237},[6866],{"type":30,"value":811},{"type":24,"tag":155,"props":6868,"children":6869},{"style":479},[6870],{"type":30,"value":6640},{"type":24,"tag":155,"props":6872,"children":6873},{"style":173},[6874],{"type":30,"value":176},{"type":24,"tag":155,"props":6876,"children":6877},{"style":479},[6878],{"type":30,"value":6832},{"type":24,"tag":155,"props":6880,"children":6881},{"style":173},[6882],{"type":30,"value":6837},{"type":24,"tag":155,"props":6884,"children":6885},{"style":3095},[6886],{"type":30,"value":6842},{"type":24,"tag":155,"props":6888,"children":6889},{"style":173},[6890],{"type":30,"value":186},{"type":24,"tag":155,"props":6892,"children":6893},{"class":157,"line":572},[6894],{"type":24,"tag":155,"props":6895,"children":6896},{"emptyLinePlaceholder":227},[6897],{"type":30,"value":230},{"type":24,"tag":155,"props":6899,"children":6900},{"class":157,"line":580},[6901,6905,6909,6913,6917,6921,6926],{"type":24,"tag":155,"props":6902,"children":6903},{"style":237},[6904],{"type":30,"value":309},{"type":24,"tag":155,"props":6906,"children":6907},{"style":161},[6908],{"type":30,"value":356},{"type":24,"tag":155,"props":6910,"children":6911},{"style":327},[6912],{"type":30,"value":5845},{"type":24,"tag":155,"props":6914,"children":6915},{"style":173},[6916],{"type":30,"value":551},{"type":24,"tag":155,"props":6918,"children":6919},{"style":167},[6920],{"type":30,"value":5714},{"type":24,"tag":155,"props":6922,"children":6923},{"style":327},[6924],{"type":30,"value":6925}," command",{"type":24,"tag":155,"props":6927,"children":6928},{"style":173},[6929],{"type":30,"value":453},{"type":24,"tag":155,"props":6931,"children":6932},{"class":157,"line":614},[6933],{"type":24,"tag":155,"props":6934,"children":6935},{"style":173},[6936],{"type":30,"value":462},{"type":24,"tag":155,"props":6938,"children":6939},{"class":157,"line":622},[6940,6945,6949,6953],{"type":24,"tag":155,"props":6941,"children":6942},{"style":479},[6943],{"type":30,"value":6944},"        command",{"type":24,"tag":155,"props":6946,"children":6947},{"style":173},[6948],{"type":30,"value":176},{"type":24,"tag":155,"props":6950,"children":6951},{"style":327},[6952],{"type":30,"value":6485},{"type":24,"tag":155,"props":6954,"children":6955},{"style":173},[6956],{"type":30,"value":516},{"type":24,"tag":155,"props":6958,"children":6959},{"class":157,"line":680},[6960,6965,6969,6974,6978,6983],{"type":24,"tag":155,"props":6961,"children":6962},{"style":479},[6963],{"type":30,"value":6964},"        _undoStack",{"type":24,"tag":155,"props":6966,"children":6967},{"style":173},[6968],{"type":30,"value":176},{"type":24,"tag":155,"props":6970,"children":6971},{"style":327},[6972],{"type":30,"value":6973},"Push",{"type":24,"tag":155,"props":6975,"children":6976},{"style":173},[6977],{"type":30,"value":551},{"type":24,"tag":155,"props":6979,"children":6980},{"style":479},[6981],{"type":30,"value":6982},"command",{"type":24,"tag":155,"props":6984,"children":6985},{"style":173},[6986],{"type":30,"value":560},{"type":24,"tag":155,"props":6988,"children":6989},{"class":157,"line":688},[6990,6995,6999,7004],{"type":24,"tag":155,"props":6991,"children":6992},{"style":479},[6993],{"type":30,"value":6994},"        _redoStack",{"type":24,"tag":155,"props":6996,"children":6997},{"style":173},[6998],{"type":30,"value":176},{"type":24,"tag":155,"props":7000,"children":7001},{"style":327},[7002],{"type":30,"value":7003},"Clear",{"type":24,"tag":155,"props":7005,"children":7006},{"style":173},[7007],{"type":30,"value":516},{"type":24,"tag":155,"props":7009,"children":7011},{"class":157,"line":7010},22,[7012,7017],{"type":24,"tag":155,"props":7013,"children":7014},{"style":327},[7015],{"type":30,"value":7016},"        TrimHistory",{"type":24,"tag":155,"props":7018,"children":7019},{"style":173},[7020],{"type":30,"value":516},{"type":24,"tag":155,"props":7022,"children":7024},{"class":157,"line":7023},23,[7025,7030,7034,7038,7042],{"type":24,"tag":155,"props":7026,"children":7027},{"style":479},[7028],{"type":30,"value":7029},"        StateChanged",{"type":24,"tag":155,"props":7031,"children":7032},{"style":237},[7033],{"type":30,"value":324},{"type":24,"tag":155,"props":7035,"children":7036},{"style":173},[7037],{"type":30,"value":176},{"type":24,"tag":155,"props":7039,"children":7040},{"style":327},[7041],{"type":30,"value":641},{"type":24,"tag":155,"props":7043,"children":7044},{"style":173},[7045],{"type":30,"value":516},{"type":24,"tag":155,"props":7047,"children":7049},{"class":157,"line":7048},24,[7050],{"type":24,"tag":155,"props":7051,"children":7052},{"style":173},[7053],{"type":30,"value":569},{"type":24,"tag":155,"props":7055,"children":7057},{"class":157,"line":7056},25,[7058],{"type":24,"tag":155,"props":7059,"children":7060},{"emptyLinePlaceholder":227},[7061],{"type":30,"value":230},{"type":24,"tag":155,"props":7063,"children":7065},{"class":157,"line":7064},26,[7066,7070,7074,7078],{"type":24,"tag":155,"props":7067,"children":7068},{"style":237},[7069],{"type":30,"value":309},{"type":24,"tag":155,"props":7071,"children":7072},{"style":161},[7073],{"type":30,"value":356},{"type":24,"tag":155,"props":7075,"children":7076},{"style":327},[7077],{"type":30,"value":5904},{"type":24,"tag":155,"props":7079,"children":7080},{"style":173},[7081],{"type":30,"value":7082},"()\n",{"type":24,"tag":155,"props":7084,"children":7086},{"class":157,"line":7085},27,[7087],{"type":24,"tag":155,"props":7088,"children":7089},{"style":173},[7090],{"type":30,"value":462},{"type":24,"tag":155,"props":7092,"children":7094},{"class":157,"line":7093},28,[7095,7099,7103,7107,7112,7116,7121],{"type":24,"tag":155,"props":7096,"children":7097},{"style":161},[7098],{"type":30,"value":471},{"type":24,"tag":155,"props":7100,"children":7101},{"style":173},[7102],{"type":30,"value":476},{"type":24,"tag":155,"props":7104,"children":7105},{"style":237},[7106],{"type":30,"value":1716},{"type":24,"tag":155,"props":7108,"children":7109},{"style":479},[7110],{"type":30,"value":7111},"CanUndo",{"type":24,"tag":155,"props":7113,"children":7114},{"style":173},[7115],{"type":30,"value":496},{"type":24,"tag":155,"props":7117,"children":7118},{"style":161},[7119],{"type":30,"value":7120}," return",{"type":24,"tag":155,"props":7122,"children":7123},{"style":173},[7124],{"type":30,"value":186},{"type":24,"tag":155,"props":7126,"children":7128},{"class":157,"line":7127},29,[7129,7134,7138,7142,7146,7150,7155],{"type":24,"tag":155,"props":7130,"children":7131},{"style":237},[7132],{"type":30,"value":7133},"        var",{"type":24,"tag":155,"props":7135,"children":7136},{"style":327},[7137],{"type":30,"value":6925},{"type":24,"tag":155,"props":7139,"children":7140},{"style":173},[7141],{"type":30,"value":443},{"type":24,"tag":155,"props":7143,"children":7144},{"style":479},[7145],{"type":30,"value":6596},{"type":24,"tag":155,"props":7147,"children":7148},{"style":173},[7149],{"type":30,"value":176},{"type":24,"tag":155,"props":7151,"children":7152},{"style":327},[7153],{"type":30,"value":7154},"Pop",{"type":24,"tag":155,"props":7156,"children":7157},{"style":173},[7158],{"type":30,"value":516},{"type":24,"tag":155,"props":7160,"children":7162},{"class":157,"line":7161},30,[7163,7167,7171,7175],{"type":24,"tag":155,"props":7164,"children":7165},{"style":479},[7166],{"type":30,"value":6944},{"type":24,"tag":155,"props":7168,"children":7169},{"style":173},[7170],{"type":30,"value":176},{"type":24,"tag":155,"props":7172,"children":7173},{"style":327},[7174],{"type":30,"value":6492},{"type":24,"tag":155,"props":7176,"children":7177},{"style":173},[7178],{"type":30,"value":516},{"type":24,"tag":155,"props":7180,"children":7182},{"class":157,"line":7181},31,[7183,7187,7191,7195,7199,7203],{"type":24,"tag":155,"props":7184,"children":7185},{"style":479},[7186],{"type":30,"value":6994},{"type":24,"tag":155,"props":7188,"children":7189},{"style":173},[7190],{"type":30,"value":176},{"type":24,"tag":155,"props":7192,"children":7193},{"style":327},[7194],{"type":30,"value":6973},{"type":24,"tag":155,"props":7196,"children":7197},{"style":173},[7198],{"type":30,"value":551},{"type":24,"tag":155,"props":7200,"children":7201},{"style":479},[7202],{"type":30,"value":6982},{"type":24,"tag":155,"props":7204,"children":7205},{"style":173},[7206],{"type":30,"value":560},{"type":24,"tag":155,"props":7208,"children":7210},{"class":157,"line":7209},32,[7211,7215,7219,7223,7227],{"type":24,"tag":155,"props":7212,"children":7213},{"style":479},[7214],{"type":30,"value":7029},{"type":24,"tag":155,"props":7216,"children":7217},{"style":237},[7218],{"type":30,"value":324},{"type":24,"tag":155,"props":7220,"children":7221},{"style":173},[7222],{"type":30,"value":176},{"type":24,"tag":155,"props":7224,"children":7225},{"style":327},[7226],{"type":30,"value":641},{"type":24,"tag":155,"props":7228,"children":7229},{"style":173},[7230],{"type":30,"value":516},{"type":24,"tag":155,"props":7232,"children":7234},{"class":157,"line":7233},33,[7235],{"type":24,"tag":155,"props":7236,"children":7237},{"style":173},[7238],{"type":30,"value":569},{"type":24,"tag":155,"props":7240,"children":7242},{"class":157,"line":7241},34,[7243],{"type":24,"tag":155,"props":7244,"children":7245},{"emptyLinePlaceholder":227},[7246],{"type":30,"value":230},{"type":24,"tag":155,"props":7248,"children":7250},{"class":157,"line":7249},35,[7251,7255,7259,7264],{"type":24,"tag":155,"props":7252,"children":7253},{"style":237},[7254],{"type":30,"value":309},{"type":24,"tag":155,"props":7256,"children":7257},{"style":161},[7258],{"type":30,"value":356},{"type":24,"tag":155,"props":7260,"children":7261},{"style":327},[7262],{"type":30,"value":7263}," Redo",{"type":24,"tag":155,"props":7265,"children":7266},{"style":173},[7267],{"type":30,"value":7082},{"type":24,"tag":155,"props":7269,"children":7271},{"class":157,"line":7270},36,[7272],{"type":24,"tag":155,"props":7273,"children":7274},{"style":173},[7275],{"type":30,"value":462},{"type":24,"tag":155,"props":7277,"children":7279},{"class":157,"line":7278},37,[7280,7284,7288,7292,7297,7301,7305],{"type":24,"tag":155,"props":7281,"children":7282},{"style":161},[7283],{"type":30,"value":471},{"type":24,"tag":155,"props":7285,"children":7286},{"style":173},[7287],{"type":30,"value":476},{"type":24,"tag":155,"props":7289,"children":7290},{"style":237},[7291],{"type":30,"value":1716},{"type":24,"tag":155,"props":7293,"children":7294},{"style":479},[7295],{"type":30,"value":7296},"CanRedo",{"type":24,"tag":155,"props":7298,"children":7299},{"style":173},[7300],{"type":30,"value":496},{"type":24,"tag":155,"props":7302,"children":7303},{"style":161},[7304],{"type":30,"value":7120},{"type":24,"tag":155,"props":7306,"children":7307},{"style":173},[7308],{"type":30,"value":186},{"type":24,"tag":155,"props":7310,"children":7312},{"class":157,"line":7311},38,[7313,7317,7321,7325,7329,7333,7337],{"type":24,"tag":155,"props":7314,"children":7315},{"style":237},[7316],{"type":30,"value":7133},{"type":24,"tag":155,"props":7318,"children":7319},{"style":327},[7320],{"type":30,"value":6925},{"type":24,"tag":155,"props":7322,"children":7323},{"style":173},[7324],{"type":30,"value":443},{"type":24,"tag":155,"props":7326,"children":7327},{"style":479},[7328],{"type":30,"value":6640},{"type":24,"tag":155,"props":7330,"children":7331},{"style":173},[7332],{"type":30,"value":176},{"type":24,"tag":155,"props":7334,"children":7335},{"style":327},[7336],{"type":30,"value":7154},{"type":24,"tag":155,"props":7338,"children":7339},{"style":173},[7340],{"type":30,"value":516},{"type":24,"tag":155,"props":7342,"children":7344},{"class":157,"line":7343},39,[7345,7349,7353,7357],{"type":24,"tag":155,"props":7346,"children":7347},{"style":479},[7348],{"type":30,"value":6944},{"type":24,"tag":155,"props":7350,"children":7351},{"style":173},[7352],{"type":30,"value":176},{"type":24,"tag":155,"props":7354,"children":7355},{"style":327},[7356],{"type":30,"value":6485},{"type":24,"tag":155,"props":7358,"children":7359},{"style":173},[7360],{"type":30,"value":516},{"type":24,"tag":155,"props":7362,"children":7364},{"class":157,"line":7363},40,[7365,7369,7373,7377,7381,7385],{"type":24,"tag":155,"props":7366,"children":7367},{"style":479},[7368],{"type":30,"value":6964},{"type":24,"tag":155,"props":7370,"children":7371},{"style":173},[7372],{"type":30,"value":176},{"type":24,"tag":155,"props":7374,"children":7375},{"style":327},[7376],{"type":30,"value":6973},{"type":24,"tag":155,"props":7378,"children":7379},{"style":173},[7380],{"type":30,"value":551},{"type":24,"tag":155,"props":7382,"children":7383},{"style":479},[7384],{"type":30,"value":6982},{"type":24,"tag":155,"props":7386,"children":7387},{"style":173},[7388],{"type":30,"value":560},{"type":24,"tag":155,"props":7390,"children":7392},{"class":157,"line":7391},41,[7393,7397,7401,7405,7409],{"type":24,"tag":155,"props":7394,"children":7395},{"style":479},[7396],{"type":30,"value":7029},{"type":24,"tag":155,"props":7398,"children":7399},{"style":237},[7400],{"type":30,"value":324},{"type":24,"tag":155,"props":7402,"children":7403},{"style":173},[7404],{"type":30,"value":176},{"type":24,"tag":155,"props":7406,"children":7407},{"style":327},[7408],{"type":30,"value":641},{"type":24,"tag":155,"props":7410,"children":7411},{"style":173},[7412],{"type":30,"value":516},{"type":24,"tag":155,"props":7414,"children":7416},{"class":157,"line":7415},42,[7417],{"type":24,"tag":155,"props":7418,"children":7419},{"style":173},[7420],{"type":30,"value":569},{"type":24,"tag":155,"props":7422,"children":7424},{"class":157,"line":7423},43,[7425],{"type":24,"tag":155,"props":7426,"children":7427},{"style":173},[7428],{"type":30,"value":694},{"type":24,"tag":32,"props":7430,"children":7431},{},[7432],{"type":30,"value":7433},"ここで注目ポイントが2つあります。",{"type":24,"tag":32,"props":7435,"children":7436},{},[7437,7439,7444,7446,7452],{"type":30,"value":7438},"1つ目は",{"type":24,"tag":46,"props":7440,"children":7442},{"className":7441},[],[7443],{"type":30,"value":6485},{"type":30,"value":7445},"の中で",{"type":24,"tag":46,"props":7447,"children":7449},{"className":7448},[],[7450],{"type":30,"value":7451},"_redoStack.Clear()",{"type":30,"value":7453},"を呼んでいる点です。Undoした後に新しい操作を実行したら、「未来の枝」（Redo可能だった履歴）は全部捨てるのが一般的な挙動です。Adobe系のアプリでUndo→別の操作をするとRedoが効かなくなるのと同じ挙動で、ユーザーが違和感なく使えます。",{"type":24,"tag":32,"props":7455,"children":7456},{},[7457,7459,7465,7467,7473,7475,7480,7481,7486],{"type":30,"value":7458},"2つ目は",{"type":24,"tag":46,"props":7460,"children":7462},{"className":7461},[],[7463],{"type":30,"value":7464},"StateChanged",{"type":30,"value":7466},"イベントです。Blazor側ではこのイベントに購読して、UndoボタンとRedoボタンの",{"type":24,"tag":46,"props":7468,"children":7470},{"className":7469},[],[7471],{"type":30,"value":7472},"disabled",{"type":30,"value":7474},"属性を",{"type":24,"tag":46,"props":7476,"children":7478},{"className":7477},[],[7479],{"type":30,"value":7111},{"type":30,"value":3458},{"type":24,"tag":46,"props":7482,"children":7484},{"className":7483},[],[7485],{"type":30,"value":7296},{"type":30,"value":7487},"に連動させます。イベント駆動にしておけば、どこからコマンドを実行してもUIが自動で追従します。",{"type":24,"tag":25,"props":7489,"children":7491},{"id":7490},"trimhistory履歴上限でメモリリークを防ぐ",[7492],{"type":30,"value":7493},"TrimHistory：履歴上限でメモリリークを防ぐ",{"type":24,"tag":32,"props":7495,"children":7496},{},[7497],{"type":30,"value":7498},"長時間編集していると履歴はどんどん積み上がります。PICOMの楽譜エディタでは、音符の配置・削除・移動だけでも1時間の編集で数百件を超えるので、履歴を無制限に持っているとメモリが膨れ上がる恐れがあります。",{"type":24,"tag":32,"props":7500,"children":7501},{},[7502,7504,7510],{"type":30,"value":7503},"そこで、最大履歴数を超えたら古いものから捨てる",{"type":24,"tag":46,"props":7505,"children":7507},{"className":7506},[],[7508],{"type":30,"value":7509},"TrimHistory",{"type":30,"value":7511},"を実装しました。",{"type":24,"tag":145,"props":7513,"children":7515},{"className":147,"code":7514,"language":149,"meta":8,"style":8},"private void TrimHistory()\n{\n    while (_undoStack.Count > _maxHistorySize)\n    {\n        var list = _undoStack.ToList();\n        list.RemoveAt(list.Count - 1);\n        _undoStack.Clear();\n        foreach (var item in list.AsEnumerable().Reverse())\n        {\n            _undoStack.Push(item);\n        }\n    }\n}\n",[7516],{"type":24,"tag":46,"props":7517,"children":7518},{"__ignoreMap":8},[7519,7539,7546,7582,7589,7622,7668,7687,7741,7749,7778,7786,7793],{"type":24,"tag":155,"props":7520,"children":7521},{"class":157,"line":18},[7522,7526,7530,7535],{"type":24,"tag":155,"props":7523,"children":7524},{"style":237},[7525],{"type":30,"value":1645},{"type":24,"tag":155,"props":7527,"children":7528},{"style":161},[7529],{"type":30,"value":356},{"type":24,"tag":155,"props":7531,"children":7532},{"style":327},[7533],{"type":30,"value":7534}," TrimHistory",{"type":24,"tag":155,"props":7536,"children":7537},{"style":173},[7538],{"type":30,"value":7082},{"type":24,"tag":155,"props":7540,"children":7541},{"class":157,"line":189},[7542],{"type":24,"tag":155,"props":7543,"children":7544},{"style":173},[7545],{"type":30,"value":300},{"type":24,"tag":155,"props":7547,"children":7548},{"class":157,"line":223},[7549,7554,7558,7562,7566,7570,7574,7578],{"type":24,"tag":155,"props":7550,"children":7551},{"style":161},[7552],{"type":30,"value":7553},"    while",{"type":24,"tag":155,"props":7555,"children":7556},{"style":173},[7557],{"type":30,"value":476},{"type":24,"tag":155,"props":7559,"children":7560},{"style":479},[7561],{"type":30,"value":6524},{"type":24,"tag":155,"props":7563,"children":7564},{"style":173},[7565],{"type":30,"value":176},{"type":24,"tag":155,"props":7567,"children":7568},{"style":479},[7569],{"type":30,"value":6832},{"type":24,"tag":155,"props":7571,"children":7572},{"style":173},[7573],{"type":30,"value":6837},{"type":24,"tag":155,"props":7575,"children":7576},{"style":479},[7577],{"type":30,"value":6672},{"type":24,"tag":155,"props":7579,"children":7580},{"style":173},[7581],{"type":30,"value":453},{"type":24,"tag":155,"props":7583,"children":7584},{"class":157,"line":233},[7585],{"type":24,"tag":155,"props":7586,"children":7587},{"style":173},[7588],{"type":30,"value":462},{"type":24,"tag":155,"props":7590,"children":7591},{"class":157,"line":252},[7592,7596,7601,7605,7609,7613,7618],{"type":24,"tag":155,"props":7593,"children":7594},{"style":237},[7595],{"type":30,"value":7133},{"type":24,"tag":155,"props":7597,"children":7598},{"style":327},[7599],{"type":30,"value":7600}," list",{"type":24,"tag":155,"props":7602,"children":7603},{"style":173},[7604],{"type":30,"value":443},{"type":24,"tag":155,"props":7606,"children":7607},{"style":479},[7608],{"type":30,"value":6596},{"type":24,"tag":155,"props":7610,"children":7611},{"style":173},[7612],{"type":30,"value":176},{"type":24,"tag":155,"props":7614,"children":7615},{"style":327},[7616],{"type":30,"value":7617},"ToList",{"type":24,"tag":155,"props":7619,"children":7620},{"style":173},[7621],{"type":30,"value":516},{"type":24,"tag":155,"props":7623,"children":7624},{"class":157,"line":260},[7625,7630,7634,7638,7642,7647,7651,7655,7660,7664],{"type":24,"tag":155,"props":7626,"children":7627},{"style":479},[7628],{"type":30,"value":7629},"        list",{"type":24,"tag":155,"props":7631,"children":7632},{"style":173},[7633],{"type":30,"value":176},{"type":24,"tag":155,"props":7635,"children":7636},{"style":327},[7637],{"type":30,"value":6449},{"type":24,"tag":155,"props":7639,"children":7640},{"style":173},[7641],{"type":30,"value":551},{"type":24,"tag":155,"props":7643,"children":7644},{"style":479},[7645],{"type":30,"value":7646},"list",{"type":24,"tag":155,"props":7648,"children":7649},{"style":173},[7650],{"type":30,"value":176},{"type":24,"tag":155,"props":7652,"children":7653},{"style":479},[7654],{"type":30,"value":6832},{"type":24,"tag":155,"props":7656,"children":7657},{"style":237},[7658],{"type":30,"value":7659}," -",{"type":24,"tag":155,"props":7661,"children":7662},{"style":3095},[7663],{"type":30,"value":4946},{"type":24,"tag":155,"props":7665,"children":7666},{"style":173},[7667],{"type":30,"value":560},{"type":24,"tag":155,"props":7669,"children":7670},{"class":157,"line":294},[7671,7675,7679,7683],{"type":24,"tag":155,"props":7672,"children":7673},{"style":479},[7674],{"type":30,"value":6964},{"type":24,"tag":155,"props":7676,"children":7677},{"style":173},[7678],{"type":30,"value":176},{"type":24,"tag":155,"props":7680,"children":7681},{"style":327},[7682],{"type":30,"value":7003},{"type":24,"tag":155,"props":7684,"children":7685},{"style":173},[7686],{"type":30,"value":516},{"type":24,"tag":155,"props":7688,"children":7689},{"class":157,"line":303},[7690,7695,7699,7703,7708,7713,7717,7721,7726,7731,7736],{"type":24,"tag":155,"props":7691,"children":7692},{"style":161},[7693],{"type":30,"value":7694},"        foreach",{"type":24,"tag":155,"props":7696,"children":7697},{"style":173},[7698],{"type":30,"value":476},{"type":24,"tag":155,"props":7700,"children":7701},{"style":237},[7702],{"type":30,"value":2019},{"type":24,"tag":155,"props":7704,"children":7705},{"style":327},[7706],{"type":30,"value":7707}," item",{"type":24,"tag":155,"props":7709,"children":7710},{"style":161},[7711],{"type":30,"value":7712}," in",{"type":24,"tag":155,"props":7714,"children":7715},{"style":479},[7716],{"type":30,"value":7600},{"type":24,"tag":155,"props":7718,"children":7719},{"style":173},[7720],{"type":30,"value":176},{"type":24,"tag":155,"props":7722,"children":7723},{"style":327},[7724],{"type":30,"value":7725},"AsEnumerable",{"type":24,"tag":155,"props":7727,"children":7728},{"style":173},[7729],{"type":30,"value":7730},"().",{"type":24,"tag":155,"props":7732,"children":7733},{"style":327},[7734],{"type":30,"value":7735},"Reverse",{"type":24,"tag":155,"props":7737,"children":7738},{"style":173},[7739],{"type":30,"value":7740},"())\n",{"type":24,"tag":155,"props":7742,"children":7743},{"class":157,"line":337},[7744],{"type":24,"tag":155,"props":7745,"children":7746},{"style":173},[7747],{"type":30,"value":7748},"        {\n",{"type":24,"tag":155,"props":7750,"children":7751},{"class":157,"line":345},[7752,7757,7761,7765,7769,7774],{"type":24,"tag":155,"props":7753,"children":7754},{"style":479},[7755],{"type":30,"value":7756},"            _undoStack",{"type":24,"tag":155,"props":7758,"children":7759},{"style":173},[7760],{"type":30,"value":176},{"type":24,"tag":155,"props":7762,"children":7763},{"style":327},[7764],{"type":30,"value":6973},{"type":24,"tag":155,"props":7766,"children":7767},{"style":173},[7768],{"type":30,"value":551},{"type":24,"tag":155,"props":7770,"children":7771},{"style":479},[7772],{"type":30,"value":7773},"item",{"type":24,"tag":155,"props":7775,"children":7776},{"style":173},[7777],{"type":30,"value":560},{"type":24,"tag":155,"props":7779,"children":7780},{"class":157,"line":456},[7781],{"type":24,"tag":155,"props":7782,"children":7783},{"style":173},[7784],{"type":30,"value":7785},"        }\n",{"type":24,"tag":155,"props":7787,"children":7788},{"class":157,"line":465},[7789],{"type":24,"tag":155,"props":7790,"children":7791},{"style":173},[7792],{"type":30,"value":569},{"type":24,"tag":155,"props":7794,"children":7795},{"class":157,"line":519},[7796],{"type":24,"tag":155,"props":7797,"children":7798},{"style":173},[7799],{"type":30,"value":694},{"type":24,"tag":32,"props":7801,"children":7802},{},[7803,7809,7811,7816,7818,7824],{"type":24,"tag":46,"props":7804,"children":7806},{"className":7805},[],[7807],{"type":30,"value":7808},"Stack\u003CT>",{"type":30,"value":7810},"は最古要素を直接削除するAPIを持っていないので、一度",{"type":24,"tag":46,"props":7812,"children":7814},{"className":7813},[],[7815],{"type":30,"value":4868},{"type":30,"value":7817},"に展開して最後（最古）を削り、再度Pushし直しています。愚直ですが確実で、",{"type":24,"tag":46,"props":7819,"children":7821},{"className":7820},[],[7822],{"type":30,"value":7823},"_maxHistorySize = 100",{"type":30,"value":7825},"程度なら実行コストは無視できる範囲です。",{"type":24,"tag":907,"props":7827,"children":7828},{"cons-label":909,"pros-label":910},[7829,7869],{"type":24,"tag":913,"props":7830,"children":7831},{"v-slot:pros":8},[7832],{"type":24,"tag":917,"props":7833,"children":7834},{},[7835,7848,7864],{"type":24,"tag":921,"props":7836,"children":7837},{},[7838,7840,7846],{"type":30,"value":7839},"Undo/Redoの挙動が",{"type":24,"tag":46,"props":7841,"children":7843},{"className":7842},[],[7844],{"type":30,"value":7845},"Push/Pop",{"type":30,"value":7847},"と綺麗に一致して読みやすい",{"type":24,"tag":921,"props":7849,"children":7850},{},[7851,7856,7857,7862],{"type":24,"tag":46,"props":7852,"children":7854},{"className":7853},[],[7855],{"type":30,"value":7003},{"type":30,"value":3458},{"type":24,"tag":46,"props":7858,"children":7860},{"className":7859},[],[7861],{"type":30,"value":6832},{"type":30,"value":7863},"などの基本APIで十分足りる",{"type":24,"tag":921,"props":7865,"children":7866},{},[7867],{"type":30,"value":7868},"履歴構造の意図が型から自明",{"type":24,"tag":913,"props":7870,"children":7871},{"v-slot:cons":8},[7872,7885],{"type":24,"tag":921,"props":7873,"children":7874},{},[7875,7877,7883],{"type":30,"value":7876},"最古要素の削除に展開→詰め直しが必要（",{"type":24,"tag":46,"props":7878,"children":7880},{"className":7879},[],[7881],{"type":30,"value":7882},"LinkedList",{"type":30,"value":7884},"ならO(1)）",{"type":24,"tag":921,"props":7886,"children":7887},{},[7888],{"type":30,"value":7889},"双方向からの参照が欲しくなった時に不便",{"type":24,"tag":32,"props":7891,"children":7892},{},[7893,7895,7901,7903,7909],{"type":30,"value":7894},"もし履歴数が数万を想定する場合は",{"type":24,"tag":46,"props":7896,"children":7898},{"className":7897},[],[7899],{"type":30,"value":7900},"LinkedList\u003CICommand>",{"type":30,"value":7902},"や",{"type":24,"tag":46,"props":7904,"children":7906},{"className":7905},[],[7907],{"type":30,"value":7908},"Deque",{"type":30,"value":7910},"的な構造を使った方が良いのですが、エディタのUndo履歴は100件前後で十分なので、可読性を優先してStackにしています。",{"type":24,"tag":25,"props":7912,"children":7914},{"id":7913},"compositecommand複数操作を1手にまとめる",[7915],{"type":30,"value":7916},"CompositeCommand：複数操作を1手にまとめる",{"type":24,"tag":32,"props":7918,"children":7919},{},[7920,7922,7927,7929,7934,7936,7942],{"type":30,"value":7921},"実装していて一番「これがないとまずい」と感じたのが",{"type":24,"tag":46,"props":7923,"children":7925},{"className":7924},[],[7926],{"type":30,"value":5730},{"type":30,"value":7928},"です。たとえばピアノロール上で",{"type":24,"tag":38,"props":7930,"children":7931},{},[7932],{"type":30,"value":7933},"複数の音符を選択して一括で下に動かす",{"type":30,"value":7935},"操作を考えると、これを1つ1つ",{"type":24,"tag":46,"props":7937,"children":7939},{"className":7938},[],[7940],{"type":30,"value":7941},"MoveNotesCommand",{"type":30,"value":7943},"として履歴に積んだら、Undoを何回も押さないと元の状態に戻りません。",{"type":24,"tag":32,"props":7945,"children":7946},{},[7947],{"type":30,"value":7948},"解決策は「中に複数のコマンドを持ち、Execute/Undoで全部を順番に回すコマンド」を作ることです。",{"type":24,"tag":145,"props":7950,"children":7952},{"className":147,"code":7951,"language":149,"meta":8,"style":8},"public class CompositeCommand : ICommand\n{\n    private readonly List\u003CICommand> _commands;\n    private readonly string _description;\n\n    public string Description => _description;\n\n    public CompositeCommand(string description, IEnumerable\u003CICommand> commands)\n    {\n        _description = description;\n        _commands = commands.ToList();\n    }\n\n    public void Execute()\n    {\n        foreach (var command in _commands)\n        {\n            command.Execute();\n        }\n    }\n\n    public void Undo()\n    {\n        for (int i = _commands.Count - 1; i >= 0; i--)\n        {\n            _commands[i].Undo();\n        }\n    }\n}\n",[7953],{"type":24,"tag":46,"props":7954,"children":7955},{"__ignoreMap":8},[7956,7980,7987,8024,8048,8055,8082,8089,8143,8150,8170,8198,8205,8212,8231,8238,8269,8276,8296,8303,8310,8317,8336,8343,8422,8429,8459,8466,8473],{"type":24,"tag":155,"props":7957,"children":7958},{"class":157,"line":18},[7959,7963,7967,7972,7976],{"type":24,"tag":155,"props":7960,"children":7961},{"style":237},[7962],{"type":30,"value":266},{"type":24,"tag":155,"props":7964,"children":7965},{"style":237},[7966],{"type":30,"value":276},{"type":24,"tag":155,"props":7968,"children":7969},{"style":167},[7970],{"type":30,"value":7971}," CompositeCommand",{"type":24,"tag":155,"props":7973,"children":7974},{"style":173},[7975],{"type":30,"value":286},{"type":24,"tag":155,"props":7977,"children":7978},{"style":167},[7979],{"type":30,"value":5789},{"type":24,"tag":155,"props":7981,"children":7982},{"class":157,"line":189},[7983],{"type":24,"tag":155,"props":7984,"children":7985},{"style":173},[7986],{"type":30,"value":300},{"type":24,"tag":155,"props":7988,"children":7989},{"class":157,"line":223},[7990,7994,7998,8003,8007,8011,8015,8020],{"type":24,"tag":155,"props":7991,"children":7992},{"style":237},[7993],{"type":30,"value":752},{"type":24,"tag":155,"props":7995,"children":7996},{"style":237},[7997],{"type":30,"value":6093},{"type":24,"tag":155,"props":7999,"children":8000},{"style":167},[8001],{"type":30,"value":8002}," List",{"type":24,"tag":155,"props":8004,"children":8005},{"style":173},[8006],{"type":30,"value":366},{"type":24,"tag":155,"props":8008,"children":8009},{"style":167},[8010],{"type":30,"value":5714},{"type":24,"tag":155,"props":8012,"children":8013},{"style":173},[8014],{"type":30,"value":1028},{"type":24,"tag":155,"props":8016,"children":8017},{"style":327},[8018],{"type":30,"value":8019}," _commands",{"type":24,"tag":155,"props":8021,"children":8022},{"style":173},[8023],{"type":30,"value":186},{"type":24,"tag":155,"props":8025,"children":8026},{"class":157,"line":233},[8027,8031,8035,8039,8044],{"type":24,"tag":155,"props":8028,"children":8029},{"style":237},[8030],{"type":30,"value":752},{"type":24,"tag":155,"props":8032,"children":8033},{"style":237},[8034],{"type":30,"value":6093},{"type":24,"tag":155,"props":8036,"children":8037},{"style":161},[8038],{"type":30,"value":429},{"type":24,"tag":155,"props":8040,"children":8041},{"style":327},[8042],{"type":30,"value":8043}," _description",{"type":24,"tag":155,"props":8045,"children":8046},{"style":173},[8047],{"type":30,"value":186},{"type":24,"tag":155,"props":8049,"children":8050},{"class":157,"line":252},[8051],{"type":24,"tag":155,"props":8052,"children":8053},{"emptyLinePlaceholder":227},[8054],{"type":30,"value":230},{"type":24,"tag":155,"props":8056,"children":8057},{"class":157,"line":260},[8058,8062,8066,8070,8074,8078],{"type":24,"tag":155,"props":8059,"children":8060},{"style":237},[8061],{"type":30,"value":309},{"type":24,"tag":155,"props":8063,"children":8064},{"style":161},[8065],{"type":30,"value":429},{"type":24,"tag":155,"props":8067,"children":8068},{"style":327},[8069],{"type":30,"value":5964},{"type":24,"tag":155,"props":8071,"children":8072},{"style":237},[8073],{"type":30,"value":811},{"type":24,"tag":155,"props":8075,"children":8076},{"style":479},[8077],{"type":30,"value":8043},{"type":24,"tag":155,"props":8079,"children":8080},{"style":173},[8081],{"type":30,"value":186},{"type":24,"tag":155,"props":8083,"children":8084},{"class":157,"line":294},[8085],{"type":24,"tag":155,"props":8086,"children":8087},{"emptyLinePlaceholder":227},[8088],{"type":30,"value":230},{"type":24,"tag":155,"props":8090,"children":8091},{"class":157,"line":303},[8092,8096,8100,8104,8108,8113,8117,8122,8126,8130,8134,8139],{"type":24,"tag":155,"props":8093,"children":8094},{"style":237},[8095],{"type":30,"value":309},{"type":24,"tag":155,"props":8097,"children":8098},{"style":327},[8099],{"type":30,"value":7971},{"type":24,"tag":155,"props":8101,"children":8102},{"style":173},[8103],{"type":30,"value":551},{"type":24,"tag":155,"props":8105,"children":8106},{"style":161},[8107],{"type":30,"value":603},{"type":24,"tag":155,"props":8109,"children":8110},{"style":327},[8111],{"type":30,"value":8112}," description",{"type":24,"tag":155,"props":8114,"children":8115},{"style":173},[8116],{"type":30,"value":396},{"type":24,"tag":155,"props":8118,"children":8119},{"style":167},[8120],{"type":30,"value":8121}," IEnumerable",{"type":24,"tag":155,"props":8123,"children":8124},{"style":173},[8125],{"type":30,"value":366},{"type":24,"tag":155,"props":8127,"children":8128},{"style":167},[8129],{"type":30,"value":5714},{"type":24,"tag":155,"props":8131,"children":8132},{"style":173},[8133],{"type":30,"value":1028},{"type":24,"tag":155,"props":8135,"children":8136},{"style":327},[8137],{"type":30,"value":8138}," commands",{"type":24,"tag":155,"props":8140,"children":8141},{"style":173},[8142],{"type":30,"value":453},{"type":24,"tag":155,"props":8144,"children":8145},{"class":157,"line":337},[8146],{"type":24,"tag":155,"props":8147,"children":8148},{"style":173},[8149],{"type":30,"value":462},{"type":24,"tag":155,"props":8151,"children":8152},{"class":157,"line":345},[8153,8158,8162,8166],{"type":24,"tag":155,"props":8154,"children":8155},{"style":479},[8156],{"type":30,"value":8157},"        _description",{"type":24,"tag":155,"props":8159,"children":8160},{"style":173},[8161],{"type":30,"value":443},{"type":24,"tag":155,"props":8163,"children":8164},{"style":479},[8165],{"type":30,"value":8112},{"type":24,"tag":155,"props":8167,"children":8168},{"style":173},[8169],{"type":30,"value":186},{"type":24,"tag":155,"props":8171,"children":8172},{"class":157,"line":456},[8173,8178,8182,8186,8190,8194],{"type":24,"tag":155,"props":8174,"children":8175},{"style":479},[8176],{"type":30,"value":8177},"        _commands",{"type":24,"tag":155,"props":8179,"children":8180},{"style":173},[8181],{"type":30,"value":443},{"type":24,"tag":155,"props":8183,"children":8184},{"style":479},[8185],{"type":30,"value":8138},{"type":24,"tag":155,"props":8187,"children":8188},{"style":173},[8189],{"type":30,"value":176},{"type":24,"tag":155,"props":8191,"children":8192},{"style":327},[8193],{"type":30,"value":7617},{"type":24,"tag":155,"props":8195,"children":8196},{"style":173},[8197],{"type":30,"value":516},{"type":24,"tag":155,"props":8199,"children":8200},{"class":157,"line":465},[8201],{"type":24,"tag":155,"props":8202,"children":8203},{"style":173},[8204],{"type":30,"value":569},{"type":24,"tag":155,"props":8206,"children":8207},{"class":157,"line":519},[8208],{"type":24,"tag":155,"props":8209,"children":8210},{"emptyLinePlaceholder":227},[8211],{"type":30,"value":230},{"type":24,"tag":155,"props":8213,"children":8214},{"class":157,"line":540},[8215,8219,8223,8227],{"type":24,"tag":155,"props":8216,"children":8217},{"style":237},[8218],{"type":30,"value":309},{"type":24,"tag":155,"props":8220,"children":8221},{"style":161},[8222],{"type":30,"value":356},{"type":24,"tag":155,"props":8224,"children":8225},{"style":327},[8226],{"type":30,"value":5845},{"type":24,"tag":155,"props":8228,"children":8229},{"style":173},[8230],{"type":30,"value":7082},{"type":24,"tag":155,"props":8232,"children":8233},{"class":157,"line":563},[8234],{"type":24,"tag":155,"props":8235,"children":8236},{"style":173},[8237],{"type":30,"value":462},{"type":24,"tag":155,"props":8239,"children":8240},{"class":157,"line":572},[8241,8245,8249,8253,8257,8261,8265],{"type":24,"tag":155,"props":8242,"children":8243},{"style":161},[8244],{"type":30,"value":7694},{"type":24,"tag":155,"props":8246,"children":8247},{"style":173},[8248],{"type":30,"value":476},{"type":24,"tag":155,"props":8250,"children":8251},{"style":237},[8252],{"type":30,"value":2019},{"type":24,"tag":155,"props":8254,"children":8255},{"style":327},[8256],{"type":30,"value":6925},{"type":24,"tag":155,"props":8258,"children":8259},{"style":161},[8260],{"type":30,"value":7712},{"type":24,"tag":155,"props":8262,"children":8263},{"style":479},[8264],{"type":30,"value":8019},{"type":24,"tag":155,"props":8266,"children":8267},{"style":173},[8268],{"type":30,"value":453},{"type":24,"tag":155,"props":8270,"children":8271},{"class":157,"line":580},[8272],{"type":24,"tag":155,"props":8273,"children":8274},{"style":173},[8275],{"type":30,"value":7748},{"type":24,"tag":155,"props":8277,"children":8278},{"class":157,"line":614},[8279,8284,8288,8292],{"type":24,"tag":155,"props":8280,"children":8281},{"style":479},[8282],{"type":30,"value":8283},"            command",{"type":24,"tag":155,"props":8285,"children":8286},{"style":173},[8287],{"type":30,"value":176},{"type":24,"tag":155,"props":8289,"children":8290},{"style":327},[8291],{"type":30,"value":6485},{"type":24,"tag":155,"props":8293,"children":8294},{"style":173},[8295],{"type":30,"value":516},{"type":24,"tag":155,"props":8297,"children":8298},{"class":157,"line":622},[8299],{"type":24,"tag":155,"props":8300,"children":8301},{"style":173},[8302],{"type":30,"value":7785},{"type":24,"tag":155,"props":8304,"children":8305},{"class":157,"line":680},[8306],{"type":24,"tag":155,"props":8307,"children":8308},{"style":173},[8309],{"type":30,"value":569},{"type":24,"tag":155,"props":8311,"children":8312},{"class":157,"line":688},[8313],{"type":24,"tag":155,"props":8314,"children":8315},{"emptyLinePlaceholder":227},[8316],{"type":30,"value":230},{"type":24,"tag":155,"props":8318,"children":8319},{"class":157,"line":7010},[8320,8324,8328,8332],{"type":24,"tag":155,"props":8321,"children":8322},{"style":237},[8323],{"type":30,"value":309},{"type":24,"tag":155,"props":8325,"children":8326},{"style":161},[8327],{"type":30,"value":356},{"type":24,"tag":155,"props":8329,"children":8330},{"style":327},[8331],{"type":30,"value":5904},{"type":24,"tag":155,"props":8333,"children":8334},{"style":173},[8335],{"type":30,"value":7082},{"type":24,"tag":155,"props":8337,"children":8338},{"class":157,"line":7023},[8339],{"type":24,"tag":155,"props":8340,"children":8341},{"style":173},[8342],{"type":30,"value":462},{"type":24,"tag":155,"props":8344,"children":8345},{"class":157,"line":7048},[8346,8351,8355,8359,8364,8368,8372,8376,8380,8384,8388,8392,8396,8401,8405,8409,8413,8418],{"type":24,"tag":155,"props":8347,"children":8348},{"style":161},[8349],{"type":30,"value":8350},"        for",{"type":24,"tag":155,"props":8352,"children":8353},{"style":173},[8354],{"type":30,"value":476},{"type":24,"tag":155,"props":8356,"children":8357},{"style":161},[8358],{"type":30,"value":6740},{"type":24,"tag":155,"props":8360,"children":8361},{"style":327},[8362],{"type":30,"value":8363}," i",{"type":24,"tag":155,"props":8365,"children":8366},{"style":173},[8367],{"type":30,"value":443},{"type":24,"tag":155,"props":8369,"children":8370},{"style":479},[8371],{"type":30,"value":8019},{"type":24,"tag":155,"props":8373,"children":8374},{"style":173},[8375],{"type":30,"value":176},{"type":24,"tag":155,"props":8377,"children":8378},{"style":479},[8379],{"type":30,"value":6832},{"type":24,"tag":155,"props":8381,"children":8382},{"style":237},[8383],{"type":30,"value":7659},{"type":24,"tag":155,"props":8385,"children":8386},{"style":3095},[8387],{"type":30,"value":4946},{"type":24,"tag":155,"props":8389,"children":8390},{"style":173},[8391],{"type":30,"value":1188},{"type":24,"tag":155,"props":8393,"children":8394},{"style":479},[8395],{"type":30,"value":8363},{"type":24,"tag":155,"props":8397,"children":8398},{"style":173},[8399],{"type":30,"value":8400}," >=",{"type":24,"tag":155,"props":8402,"children":8403},{"style":3095},[8404],{"type":30,"value":6842},{"type":24,"tag":155,"props":8406,"children":8407},{"style":173},[8408],{"type":30,"value":1188},{"type":24,"tag":155,"props":8410,"children":8411},{"style":479},[8412],{"type":30,"value":8363},{"type":24,"tag":155,"props":8414,"children":8415},{"style":237},[8416],{"type":30,"value":8417},"--",{"type":24,"tag":155,"props":8419,"children":8420},{"style":173},[8421],{"type":30,"value":453},{"type":24,"tag":155,"props":8423,"children":8424},{"class":157,"line":7056},[8425],{"type":24,"tag":155,"props":8426,"children":8427},{"style":173},[8428],{"type":30,"value":7748},{"type":24,"tag":155,"props":8430,"children":8431},{"class":157,"line":7064},[8432,8437,8441,8446,8451,8455],{"type":24,"tag":155,"props":8433,"children":8434},{"style":479},[8435],{"type":30,"value":8436},"            _commands",{"type":24,"tag":155,"props":8438,"children":8439},{"style":173},[8440],{"type":30,"value":3042},{"type":24,"tag":155,"props":8442,"children":8443},{"style":479},[8444],{"type":30,"value":8445},"i",{"type":24,"tag":155,"props":8447,"children":8448},{"style":173},[8449],{"type":30,"value":8450},"].",{"type":24,"tag":155,"props":8452,"children":8453},{"style":327},[8454],{"type":30,"value":6492},{"type":24,"tag":155,"props":8456,"children":8457},{"style":173},[8458],{"type":30,"value":516},{"type":24,"tag":155,"props":8460,"children":8461},{"class":157,"line":7085},[8462],{"type":24,"tag":155,"props":8463,"children":8464},{"style":173},[8465],{"type":30,"value":7785},{"type":24,"tag":155,"props":8467,"children":8468},{"class":157,"line":7093},[8469],{"type":24,"tag":155,"props":8470,"children":8471},{"style":173},[8472],{"type":30,"value":569},{"type":24,"tag":155,"props":8474,"children":8475},{"class":157,"line":7127},[8476],{"type":24,"tag":155,"props":8477,"children":8478},{"style":173},[8479],{"type":30,"value":694},{"type":24,"tag":32,"props":8481,"children":8482},{},[8483,8485,8490],{"type":30,"value":8484},"地味に大事なのが",{"type":24,"tag":38,"props":8486,"children":8487},{},[8488],{"type":30,"value":8489},"Undo時は逆順で回す",{"type":30,"value":8491},"ところです。「AをしてからBをした」なら、戻す時は「Bを戻してからAを戻す」のが正しい順序です。コマンド間に副作用の依存があるとこの順序を間違えた時にバグるので、注意ポイントです。",{"type":24,"tag":32,"props":8493,"children":8494},{},[8495],{"type":30,"value":8496},"使い方はこんな感じです。",{"type":24,"tag":145,"props":8498,"children":8500},{"className":147,"code":8499,"language":149,"meta":8,"style":8},"var commands = selectedNotes.Select(note => new MoveNoteCommand(track, note, delta));\nvar composite = new CompositeCommand($\"{selectedNotes.Count}個の音符を移動\", commands);\n_undoRedoManager.Execute(composite);\n",[8501],{"type":24,"tag":46,"props":8502,"children":8503},{"__ignoreMap":8},[8504,8585,8659],{"type":24,"tag":155,"props":8505,"children":8506},{"class":157,"line":18},[8507,8511,8515,8519,8524,8528,8532,8536,8541,8545,8549,8554,8558,8563,8567,8572,8576,8581],{"type":24,"tag":155,"props":8508,"children":8509},{"style":237},[8510],{"type":30,"value":2019},{"type":24,"tag":155,"props":8512,"children":8513},{"style":327},[8514],{"type":30,"value":8138},{"type":24,"tag":155,"props":8516,"children":8517},{"style":173},[8518],{"type":30,"value":443},{"type":24,"tag":155,"props":8520,"children":8521},{"style":479},[8522],{"type":30,"value":8523}," selectedNotes",{"type":24,"tag":155,"props":8525,"children":8526},{"style":173},[8527],{"type":30,"value":176},{"type":24,"tag":155,"props":8529,"children":8530},{"style":327},[8531],{"type":30,"value":5005},{"type":24,"tag":155,"props":8533,"children":8534},{"style":173},[8535],{"type":30,"value":551},{"type":24,"tag":155,"props":8537,"children":8538},{"style":327},[8539],{"type":30,"value":8540},"note",{"type":24,"tag":155,"props":8542,"children":8543},{"style":237},[8544],{"type":30,"value":811},{"type":24,"tag":155,"props":8546,"children":8547},{"style":237},[8548],{"type":30,"value":506},{"type":24,"tag":155,"props":8550,"children":8551},{"style":167},[8552],{"type":30,"value":8553}," MoveNoteCommand",{"type":24,"tag":155,"props":8555,"children":8556},{"style":173},[8557],{"type":30,"value":551},{"type":24,"tag":155,"props":8559,"children":8560},{"style":479},[8561],{"type":30,"value":8562},"track",{"type":24,"tag":155,"props":8564,"children":8565},{"style":173},[8566],{"type":30,"value":396},{"type":24,"tag":155,"props":8568,"children":8569},{"style":479},[8570],{"type":30,"value":8571}," note",{"type":24,"tag":155,"props":8573,"children":8574},{"style":173},[8575],{"type":30,"value":396},{"type":24,"tag":155,"props":8577,"children":8578},{"style":479},[8579],{"type":30,"value":8580}," delta",{"type":24,"tag":155,"props":8582,"children":8583},{"style":173},[8584],{"type":30,"value":677},{"type":24,"tag":155,"props":8586,"children":8587},{"class":157,"line":189},[8588,8592,8597,8601,8605,8609,8613,8617,8621,8626,8630,8634,8638,8643,8647,8651,8655],{"type":24,"tag":155,"props":8589,"children":8590},{"style":237},[8591],{"type":30,"value":2019},{"type":24,"tag":155,"props":8593,"children":8594},{"style":327},[8595],{"type":30,"value":8596}," composite",{"type":24,"tag":155,"props":8598,"children":8599},{"style":173},[8600],{"type":30,"value":443},{"type":24,"tag":155,"props":8602,"children":8603},{"style":237},[8604],{"type":30,"value":506},{"type":24,"tag":155,"props":8606,"children":8607},{"style":167},[8608],{"type":30,"value":7971},{"type":24,"tag":155,"props":8610,"children":8611},{"style":173},[8612],{"type":30,"value":551},{"type":24,"tag":155,"props":8614,"children":8615},{"style":768},[8616],{"type":30,"value":4340},{"type":24,"tag":155,"props":8618,"children":8619},{"style":173},[8620],{"type":30,"value":4350},{"type":24,"tag":155,"props":8622,"children":8623},{"style":2239},[8624],{"type":30,"value":8625},"selectedNotes",{"type":24,"tag":155,"props":8627,"children":8628},{"style":173},[8629],{"type":30,"value":176},{"type":24,"tag":155,"props":8631,"children":8632},{"style":2239},[8633],{"type":30,"value":6832},{"type":24,"tag":155,"props":8635,"children":8636},{"style":173},[8637],{"type":30,"value":4368},{"type":24,"tag":155,"props":8639,"children":8640},{"style":2239},[8641],{"type":30,"value":8642},"個の音符を移動",{"type":24,"tag":155,"props":8644,"children":8645},{"style":768},[8646],{"type":30,"value":2246},{"type":24,"tag":155,"props":8648,"children":8649},{"style":173},[8650],{"type":30,"value":396},{"type":24,"tag":155,"props":8652,"children":8653},{"style":479},[8654],{"type":30,"value":8138},{"type":24,"tag":155,"props":8656,"children":8657},{"style":173},[8658],{"type":30,"value":560},{"type":24,"tag":155,"props":8660,"children":8661},{"class":157,"line":223},[8662,8667,8671,8675,8679,8684],{"type":24,"tag":155,"props":8663,"children":8664},{"style":479},[8665],{"type":30,"value":8666},"_undoRedoManager",{"type":24,"tag":155,"props":8668,"children":8669},{"style":173},[8670],{"type":30,"value":176},{"type":24,"tag":155,"props":8672,"children":8673},{"style":327},[8674],{"type":30,"value":6485},{"type":24,"tag":155,"props":8676,"children":8677},{"style":173},[8678],{"type":30,"value":551},{"type":24,"tag":155,"props":8680,"children":8681},{"style":479},[8682],{"type":30,"value":8683},"composite",{"type":24,"tag":155,"props":8685,"children":8686},{"style":173},[8687],{"type":30,"value":560},{"type":24,"tag":32,"props":8689,"children":8690},{},[8691],{"type":30,"value":8692},"ユーザー視点では「選択した音符を全部動かす」を1手としてUndoできるので、快適に使えます。",{"type":24,"tag":25,"props":8694,"children":8695},{"id":2679},[8696],{"type":30,"value":2679},{"type":24,"tag":917,"props":8698,"children":8699},{},[8700,8729,8739,8744],{"type":24,"tag":921,"props":8701,"children":8702},{},[8703,8708,8710,8715,8716,8721,8722,8727],{"type":24,"tag":46,"props":8704,"children":8706},{"className":8705},[],[8707],{"type":30,"value":5714},{"type":30,"value":8709},"は",{"type":24,"tag":46,"props":8711,"children":8713},{"className":8712},[],[8714],{"type":30,"value":6485},{"type":30,"value":3458},{"type":24,"tag":46,"props":8717,"children":8719},{"className":8718},[],[8720],{"type":30,"value":6492},{"type":30,"value":3458},{"type":24,"tag":46,"props":8723,"children":8725},{"className":8724},[],[8726],{"type":30,"value":5998},{"type":30,"value":8728},"の3点セットで十分",{"type":24,"tag":921,"props":8730,"children":8731},{},[8732,8737],{"type":24,"tag":46,"props":8733,"children":8735},{"className":8734},[],[8736],{"type":30,"value":5722},{"type":30,"value":8738},"は2本のStackで管理、Redoスタックは新規Executeで破棄",{"type":24,"tag":921,"props":8740,"children":8741},{},[8742],{"type":30,"value":8743},"履歴上限を入れないとメモリ使用量が膨らむことに注意",{"type":24,"tag":921,"props":8745,"children":8746},{},[8747,8749,8754],{"type":30,"value":8748},"複数操作を一括でする",{"type":24,"tag":46,"props":8750,"children":8752},{"className":8751},[],[8753],{"type":30,"value":5730},{"type":30,"value":8755},"は必須レベルで便利。Undo時は逆順に回す",{"type":24,"tag":2721,"props":8757,"children":8758},{},[8759],{"type":30,"value":2725},{"title":8,"searchDepth":189,"depth":189,"links":8761},[8762,8763,8764,8765,8766,8767,8768],{"id":27,"depth":189,"text":27},{"id":5735,"depth":189,"text":5738},{"id":5758,"depth":189,"text":5761},{"id":6504,"depth":189,"text":6507},{"id":7490,"depth":189,"text":7493},{"id":7913,"depth":189,"text":7916},{"id":2679,"depth":189,"text":2679},"content:articles:tech:blazor:undo-redo-command-pattern.md","articles/tech/blazor/undo-redo-command-pattern.md","articles/tech/blazor/undo-redo-command-pattern",{"_path":8773,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":8774,"description":8775,"date":8776,"tags":8777,"rowTypeId":18,"sitemap":8780,"body":8781,"_type":2738,"_id":11026,"_source":2740,"_file":11027,"_stem":11028,"_extension":2743},"/articles/tech/blazor/blazor-wasm-multithreading","Blazor WebAssemblyでマルチスレッドを有効化してUIフリーズを解消する","Blazor WebAssemblyでWasmEnableThreadsを有効化し、音楽生成をバックグラウンドスレッドにオフロードしてUIフリーズを解消した実例です。COOP/COEPの必要性、Channel\u003CT>によるストリーミング再生、PeriodicTimerへの移行で踏んだ不具合も紹介します。","2026-04-18",[13,15,8778,8779,17],"WebAssembly","マルチスレッド",{"loc":8773,"lastmod":8776,"priority":18},{"type":21,"children":8782,"toc":11016},[8783,8788,8793,8810,8849,8854,8872,8878,8891,8932,8945,8951,8968,8974,8986,8991,9068,9073,9079,9084,9116,9121,9126,9131,9554,9559,9564,9577,9590,9615,9622,9627,9633,10377,10383,10622,10627,10633,10638,10657,10679,10927,10933,10938,10943,10947,11012],{"type":24,"tag":25,"props":8784,"children":8786},{"id":8785},"背景と目的",[8787],{"type":30,"value":8785},{"type":24,"tag":32,"props":8789,"children":8790},{},[8791],{"type":30,"value":8792},"PICOMでは、音を生成しながらピアノロールなどGUIの描画処理を走らせる必要があります。しかし、単純に非同期処理をするだけでは、厳密なマルチスレッド処理にはならず、UIスレッドで音楽の生成をしてしまいます。その結果、音楽生成中にUIがフリーズしていた問題が発生しました。",{"type":24,"tag":32,"props":8794,"children":8795},{},[8796,8798,8808],{"type":30,"value":8797},"そこで、",{"type":24,"tag":83,"props":8799,"children":8801},{"content":8800},"Blazor WebAssemblyで本物のOSスレッドを有効化するMSBuildプロパティ",[8802],{"type":24,"tag":46,"props":8803,"children":8805},{"className":8804},[],[8806],{"type":30,"value":8807},"WasmEnableThreads",{"type":30,"value":8809}," を有効化し、音楽生成をバックグラウンドスレッドにオフロードすることで、UI描画と音楽生成を分離する対策を行いました。",{"type":24,"tag":92,"props":8811,"children":8812},{},[8813],{"type":24,"tag":32,"props":8814,"children":8815},{},[8816,8818,8823,8825,8831,8833,8839,8841,8847],{"type":30,"value":8817},"Blazor WebAssembly の ",{"type":24,"tag":46,"props":8819,"children":8821},{"className":8820},[],[8822],{"type":30,"value":8807},{"type":30,"value":8824}," を有効化し、",{"type":24,"tag":46,"props":8826,"children":8828},{"className":8827},[],[8829],{"type":30,"value":8830},"Channel\u003CT>",{"type":30,"value":8832}," による Producer-Consumer パターンで音声のストリーミング再生を実現しました。COOP/COEP ヘッダーの設定や、",{"type":24,"tag":46,"props":8834,"children":8836},{"className":8835},[],[8837],{"type":30,"value":8838},"System.Timers.Timer",{"type":30,"value":8840}," から ",{"type":24,"tag":46,"props":8842,"children":8844},{"className":8843},[],[8845],{"type":30,"value":8846},"PeriodicTimer",{"type":30,"value":8848}," への移行で踏んだ不具合の解決も紹介します。",{"type":24,"tag":25,"props":8850,"children":8852},{"id":8851},"前提環境",[8853],{"type":30,"value":8851},{"type":24,"tag":917,"props":8855,"children":8856},{},[8857,8862,8867],{"type":24,"tag":921,"props":8858,"children":8859},{},[8860],{"type":30,"value":8861},"Blazor WebAssembly",{"type":24,"tag":921,"props":8863,"children":8864},{},[8865],{"type":30,"value":8866},".NET 10.0",{"type":24,"tag":921,"props":8868,"children":8869},{},[8870],{"type":30,"value":8871},"音声ライブラリ: SoundMaker v3.0.0(自作の音生成ライブラリです！)",{"type":24,"tag":25,"props":8873,"children":8875},{"id":8874},"wasmenablethreads-の有効化",[8876],{"type":30,"value":8877},"WasmEnableThreads の有効化",{"type":24,"tag":32,"props":8879,"children":8880},{},[8881,8883,8889],{"type":30,"value":8882},"まず、",{"type":24,"tag":46,"props":8884,"children":8886},{"className":8885},[],[8887],{"type":30,"value":8888},".csproj",{"type":30,"value":8890}," ファイルに以下を追加します。",{"type":24,"tag":145,"props":8892,"children":8896},{"className":8893,"code":8894,"language":8895,"meta":8,"style":8},"language-xml shiki shiki-themes vitesse-dark","\u003CWasmEnableThreads>true\u003C/WasmEnableThreads>\n","xml",[8897],{"type":24,"tag":46,"props":8898,"children":8899},{"__ignoreMap":8},[8900],{"type":24,"tag":155,"props":8901,"children":8902},{"class":157,"line":18},[8903,8907,8911,8915,8920,8924,8928],{"type":24,"tag":155,"props":8904,"children":8905},{"style":173},[8906],{"type":30,"value":366},{"type":24,"tag":155,"props":8908,"children":8909},{"style":161},[8910],{"type":30,"value":8807},{"type":24,"tag":155,"props":8912,"children":8913},{"style":173},[8914],{"type":30,"value":1028},{"type":24,"tag":155,"props":8916,"children":8918},{"style":8917},"--shiki-default:#DBD7CAEE",[8919],{"type":30,"value":2304},{"type":24,"tag":155,"props":8921,"children":8922},{"style":173},[8923],{"type":30,"value":1570},{"type":24,"tag":155,"props":8925,"children":8926},{"style":161},[8927],{"type":30,"value":8807},{"type":24,"tag":155,"props":8929,"children":8930},{"style":173},[8931],{"type":30,"value":1067},{"type":24,"tag":32,"props":8933,"children":8934},{},[8935,8937,8943],{"type":30,"value":8936},"これにより、",{"type":24,"tag":46,"props":8938,"children":8940},{"className":8939},[],[8941],{"type":30,"value":8942},"Task.Run",{"type":30,"value":8944}," でバックグラウンドスレッドへの処理オフロードが可能になります。",{"type":24,"tag":875,"props":8946,"children":8948},{"id":8947},"注意点1coopcoep-ヘッダーが必要",[8949],{"type":30,"value":8950},"注意点1：COOP/COEP ヘッダーが必要",{"type":24,"tag":32,"props":8952,"children":8953},{},[8954,8956,8966],{"type":30,"value":8955},"ブラウザの ",{"type":24,"tag":83,"props":8957,"children":8959},{"content":8958},"複数のスレッド間でメモリを直接共有できるJavaScriptオブジェクト。Web Workerとのデータ共有に使われる",[8960],{"type":24,"tag":46,"props":8961,"children":8963},{"className":8962},[],[8964],{"type":30,"value":8965},"SharedArrayBuffer",{"type":30,"value":8967}," を使用するため、COOP/COEP ヘッダーが必要となります。\nこのヘッダの指定方法はデプロイ先によって変わるため、本記事では設定方法を割愛します。",{"type":24,"tag":8969,"props":8970,"children":8972},"h4",{"id":8971},"マルチスレッド環境下でサイドチャネル攻撃を防ぐには",[8973],{"type":30,"value":8971},{"type":24,"tag":32,"props":8975,"children":8976},{},[8977,8979,8984],{"type":30,"value":8978},"マルチスレッドプログラミングを実現するには、スレッド間でメモリを共有する必要があります。それを実現する具体的な機能が、",{"type":24,"tag":46,"props":8980,"children":8982},{"className":8981},[],[8983],{"type":30,"value":8965},{"type":30,"value":8985},"というものです。非常に便利な機能なのですが、バックグラウンドでタイマーを実行できるようになるので高精度なタイマーが利用できるようになります。これを悪用し、CPUキャッシュアクセス時間などからデータを推測し窃取するという、サイドチャネル攻撃が可能になるという弱点があります。",{"type":24,"tag":32,"props":8987,"children":8988},{},[8989],{"type":30,"value":8990},"この問題を解決するためには、クロスオリジン分離という仕組みが必要になります。",{"type":24,"tag":8992,"props":8993,"children":8994},"table",{},[8995,9019],{"type":24,"tag":8996,"props":8997,"children":8998},"thead",{},[8999],{"type":24,"tag":9000,"props":9001,"children":9002},"tr",{},[9003,9009,9014],{"type":24,"tag":9004,"props":9005,"children":9006},"th",{},[9007],{"type":30,"value":9008},"ヘッダ",{"type":24,"tag":9004,"props":9010,"children":9011},{},[9012],{"type":30,"value":9013},"正式名称",{"type":24,"tag":9004,"props":9015,"children":9016},{},[9017],{"type":30,"value":9018},"役割",{"type":24,"tag":9020,"props":9021,"children":9022},"tbody",{},[9023,9046],{"type":24,"tag":9000,"props":9024,"children":9025},{},[9026,9032,9041],{"type":24,"tag":9027,"props":9028,"children":9029},"td",{},[9030],{"type":30,"value":9031},"COOP",{"type":24,"tag":9027,"props":9033,"children":9034},{},[9035],{"type":24,"tag":46,"props":9036,"children":9038},{"className":9037},[],[9039],{"type":30,"value":9040},"Cross-Origin-Opener-Policy",{"type":24,"tag":9027,"props":9042,"children":9043},{},[9044],{"type":30,"value":9045},"他オリジンのページと同じブラウザプロセスを共有しなくする",{"type":24,"tag":9000,"props":9047,"children":9048},{},[9049,9054,9063],{"type":24,"tag":9027,"props":9050,"children":9051},{},[9052],{"type":30,"value":9053},"COEP",{"type":24,"tag":9027,"props":9055,"children":9056},{},[9057],{"type":24,"tag":46,"props":9058,"children":9060},{"className":9059},[],[9061],{"type":30,"value":9062},"Cross-Origin-Embedder-Policy",{"type":24,"tag":9027,"props":9064,"children":9065},{},[9066],{"type":30,"value":9067},"他オリジンのリソースを読み込む際にCORSやCORPヘッダを必須にする",{"type":24,"tag":32,"props":9069,"children":9070},{},[9071],{"type":30,"value":9072},"この２つの仕組みを組み合わせることで、「他オリジンのプロセスと物理的に遮断した上で、かつリソースの読み込みに制限をかける」ことができるので、仮に高精度なタイマーを使って攻撃しようとしても、そもそも他オリジンの情報を盗むことが不可能になるという仕組みです。",{"type":24,"tag":875,"props":9074,"children":9076},{"id":9075},"注意点2ワークロードのインストールが必要",[9077],{"type":30,"value":9078},"注意点2：ワークロードのインストールが必要",{"type":24,"tag":32,"props":9080,"children":9081},{},[9082],{"type":30,"value":9083},"デフォルトでは有効になっていないため、以下のコマンドでワークロードのインストールが必要です。",{"type":24,"tag":145,"props":9085,"children":9089},{"className":9086,"code":9087,"language":9088,"meta":8,"style":8},"language-bash shiki shiki-themes vitesse-dark","dotnet workload install wasm-tools\n","bash",[9090],{"type":24,"tag":46,"props":9091,"children":9092},{"__ignoreMap":8},[9093],{"type":24,"tag":155,"props":9094,"children":9095},{"class":157,"line":18},[9096,9101,9106,9111],{"type":24,"tag":155,"props":9097,"children":9098},{"style":327},[9099],{"type":30,"value":9100},"dotnet",{"type":24,"tag":155,"props":9102,"children":9103},{"style":2239},[9104],{"type":30,"value":9105}," workload",{"type":24,"tag":155,"props":9107,"children":9108},{"style":2239},[9109],{"type":30,"value":9110}," install",{"type":24,"tag":155,"props":9112,"children":9113},{"style":2239},[9114],{"type":30,"value":9115}," wasm-tools\n",{"type":24,"tag":8969,"props":9117,"children":9119},{"id":9118},"開発用サーバープロジェクトの追加",[9120],{"type":30,"value":9118},{"type":24,"tag":32,"props":9122,"children":9123},{},[9124],{"type":30,"value":9125},"Blazor WASM の DevServer は COOP/COEP ヘッダーを自動付与しないため、ASP.NET Core のホストプロジェクトを新規作成しました。そういう設定もありそうなのですが、一旦これでいいかということで妥協しています。",{"type":24,"tag":32,"props":9127,"children":9128},{},[9129],{"type":30,"value":9130},"実装例を示します。",{"type":24,"tag":145,"props":9132,"children":9134},{"className":147,"code":9133,"language":149,"meta":8,"style":8},"// Program.cs\nvar builder = WebApplication.CreateBuilder(args);\nvar app = builder.Build();\n\napp.Use(async (context, next) =>\n{\n    context.Response.Headers[\"Cross-Origin-Opener-Policy\"] = \"same-origin\";\n    context.Response.Headers[\"Cross-Origin-Embedder-Policy\"] = \"require-corp\";\n    await next();\n});\n\napp.UseBlazorFrameworkFiles();\napp.UseStaticFiles();\napp.MapFallbackToFile(\"index.html\");\n\napp.Run();\n",[9135],{"type":24,"tag":46,"props":9136,"children":9137},{"__ignoreMap":8},[9138,9146,9189,9222,9229,9281,9288,9355,9419,9435,9443,9450,9470,9490,9527,9534],{"type":24,"tag":155,"props":9139,"children":9140},{"class":157,"line":18},[9141],{"type":24,"tag":155,"props":9142,"children":9143},{"style":1411},[9144],{"type":30,"value":9145},"// Program.cs\n",{"type":24,"tag":155,"props":9147,"children":9148},{"class":157,"line":189},[9149,9153,9158,9162,9167,9171,9176,9180,9185],{"type":24,"tag":155,"props":9150,"children":9151},{"style":237},[9152],{"type":30,"value":2019},{"type":24,"tag":155,"props":9154,"children":9155},{"style":327},[9156],{"type":30,"value":9157}," builder",{"type":24,"tag":155,"props":9159,"children":9160},{"style":173},[9161],{"type":30,"value":443},{"type":24,"tag":155,"props":9163,"children":9164},{"style":479},[9165],{"type":30,"value":9166}," WebApplication",{"type":24,"tag":155,"props":9168,"children":9169},{"style":173},[9170],{"type":30,"value":176},{"type":24,"tag":155,"props":9172,"children":9173},{"style":327},[9174],{"type":30,"value":9175},"CreateBuilder",{"type":24,"tag":155,"props":9177,"children":9178},{"style":173},[9179],{"type":30,"value":551},{"type":24,"tag":155,"props":9181,"children":9182},{"style":479},[9183],{"type":30,"value":9184},"args",{"type":24,"tag":155,"props":9186,"children":9187},{"style":173},[9188],{"type":30,"value":560},{"type":24,"tag":155,"props":9190,"children":9191},{"class":157,"line":223},[9192,9196,9201,9205,9209,9213,9218],{"type":24,"tag":155,"props":9193,"children":9194},{"style":237},[9195],{"type":30,"value":2019},{"type":24,"tag":155,"props":9197,"children":9198},{"style":327},[9199],{"type":30,"value":9200}," app",{"type":24,"tag":155,"props":9202,"children":9203},{"style":173},[9204],{"type":30,"value":443},{"type":24,"tag":155,"props":9206,"children":9207},{"style":479},[9208],{"type":30,"value":9157},{"type":24,"tag":155,"props":9210,"children":9211},{"style":173},[9212],{"type":30,"value":176},{"type":24,"tag":155,"props":9214,"children":9215},{"style":327},[9216],{"type":30,"value":9217},"Build",{"type":24,"tag":155,"props":9219,"children":9220},{"style":173},[9221],{"type":30,"value":516},{"type":24,"tag":155,"props":9223,"children":9224},{"class":157,"line":233},[9225],{"type":24,"tag":155,"props":9226,"children":9227},{"emptyLinePlaceholder":227},[9228],{"type":30,"value":230},{"type":24,"tag":155,"props":9230,"children":9231},{"class":157,"line":252},[9232,9237,9241,9246,9250,9255,9259,9264,9268,9273,9277],{"type":24,"tag":155,"props":9233,"children":9234},{"style":479},[9235],{"type":30,"value":9236},"app",{"type":24,"tag":155,"props":9238,"children":9239},{"style":173},[9240],{"type":30,"value":176},{"type":24,"tag":155,"props":9242,"children":9243},{"style":327},[9244],{"type":30,"value":9245},"Use",{"type":24,"tag":155,"props":9247,"children":9248},{"style":173},[9249],{"type":30,"value":551},{"type":24,"tag":155,"props":9251,"children":9252},{"style":237},[9253],{"type":30,"value":9254},"async",{"type":24,"tag":155,"props":9256,"children":9257},{"style":173},[9258],{"type":30,"value":476},{"type":24,"tag":155,"props":9260,"children":9261},{"style":327},[9262],{"type":30,"value":9263},"context",{"type":24,"tag":155,"props":9265,"children":9266},{"style":173},[9267],{"type":30,"value":396},{"type":24,"tag":155,"props":9269,"children":9270},{"style":327},[9271],{"type":30,"value":9272}," next",{"type":24,"tag":155,"props":9274,"children":9275},{"style":173},[9276],{"type":30,"value":496},{"type":24,"tag":155,"props":9278,"children":9279},{"style":237},[9280],{"type":30,"value":2183},{"type":24,"tag":155,"props":9282,"children":9283},{"class":157,"line":260},[9284],{"type":24,"tag":155,"props":9285,"children":9286},{"style":173},[9287],{"type":30,"value":300},{"type":24,"tag":155,"props":9289,"children":9290},{"class":157,"line":294},[9291,9296,9300,9305,9309,9314,9318,9322,9326,9330,9334,9338,9342,9347,9351],{"type":24,"tag":155,"props":9292,"children":9293},{"style":479},[9294],{"type":30,"value":9295},"    context",{"type":24,"tag":155,"props":9297,"children":9298},{"style":173},[9299],{"type":30,"value":176},{"type":24,"tag":155,"props":9301,"children":9302},{"style":479},[9303],{"type":30,"value":9304},"Response",{"type":24,"tag":155,"props":9306,"children":9307},{"style":173},[9308],{"type":30,"value":176},{"type":24,"tag":155,"props":9310,"children":9311},{"style":479},[9312],{"type":30,"value":9313},"Headers",{"type":24,"tag":155,"props":9315,"children":9316},{"style":173},[9317],{"type":30,"value":3042},{"type":24,"tag":155,"props":9319,"children":9320},{"style":768},[9321],{"type":30,"value":2246},{"type":24,"tag":155,"props":9323,"children":9324},{"style":2239},[9325],{"type":30,"value":9040},{"type":24,"tag":155,"props":9327,"children":9328},{"style":768},[9329],{"type":30,"value":2246},{"type":24,"tag":155,"props":9331,"children":9332},{"style":173},[9333],{"type":30,"value":424},{"type":24,"tag":155,"props":9335,"children":9336},{"style":173},[9337],{"type":30,"value":443},{"type":24,"tag":155,"props":9339,"children":9340},{"style":768},[9341],{"type":30,"value":2236},{"type":24,"tag":155,"props":9343,"children":9344},{"style":2239},[9345],{"type":30,"value":9346},"same-origin",{"type":24,"tag":155,"props":9348,"children":9349},{"style":768},[9350],{"type":30,"value":2246},{"type":24,"tag":155,"props":9352,"children":9353},{"style":173},[9354],{"type":30,"value":186},{"type":24,"tag":155,"props":9356,"children":9357},{"class":157,"line":303},[9358,9362,9366,9370,9374,9378,9382,9386,9390,9394,9398,9402,9406,9411,9415],{"type":24,"tag":155,"props":9359,"children":9360},{"style":479},[9361],{"type":30,"value":9295},{"type":24,"tag":155,"props":9363,"children":9364},{"style":173},[9365],{"type":30,"value":176},{"type":24,"tag":155,"props":9367,"children":9368},{"style":479},[9369],{"type":30,"value":9304},{"type":24,"tag":155,"props":9371,"children":9372},{"style":173},[9373],{"type":30,"value":176},{"type":24,"tag":155,"props":9375,"children":9376},{"style":479},[9377],{"type":30,"value":9313},{"type":24,"tag":155,"props":9379,"children":9380},{"style":173},[9381],{"type":30,"value":3042},{"type":24,"tag":155,"props":9383,"children":9384},{"style":768},[9385],{"type":30,"value":2246},{"type":24,"tag":155,"props":9387,"children":9388},{"style":2239},[9389],{"type":30,"value":9062},{"type":24,"tag":155,"props":9391,"children":9392},{"style":768},[9393],{"type":30,"value":2246},{"type":24,"tag":155,"props":9395,"children":9396},{"style":173},[9397],{"type":30,"value":424},{"type":24,"tag":155,"props":9399,"children":9400},{"style":173},[9401],{"type":30,"value":443},{"type":24,"tag":155,"props":9403,"children":9404},{"style":768},[9405],{"type":30,"value":2236},{"type":24,"tag":155,"props":9407,"children":9408},{"style":2239},[9409],{"type":30,"value":9410},"require-corp",{"type":24,"tag":155,"props":9412,"children":9413},{"style":768},[9414],{"type":30,"value":2246},{"type":24,"tag":155,"props":9416,"children":9417},{"style":173},[9418],{"type":30,"value":186},{"type":24,"tag":155,"props":9420,"children":9421},{"class":157,"line":337},[9422,9427,9431],{"type":24,"tag":155,"props":9423,"children":9424},{"style":237},[9425],{"type":30,"value":9426},"    await",{"type":24,"tag":155,"props":9428,"children":9429},{"style":327},[9430],{"type":30,"value":9272},{"type":24,"tag":155,"props":9432,"children":9433},{"style":173},[9434],{"type":30,"value":516},{"type":24,"tag":155,"props":9436,"children":9437},{"class":157,"line":345},[9438],{"type":24,"tag":155,"props":9439,"children":9440},{"style":173},[9441],{"type":30,"value":9442},"});\n",{"type":24,"tag":155,"props":9444,"children":9445},{"class":157,"line":456},[9446],{"type":24,"tag":155,"props":9447,"children":9448},{"emptyLinePlaceholder":227},[9449],{"type":30,"value":230},{"type":24,"tag":155,"props":9451,"children":9452},{"class":157,"line":465},[9453,9457,9461,9466],{"type":24,"tag":155,"props":9454,"children":9455},{"style":479},[9456],{"type":30,"value":9236},{"type":24,"tag":155,"props":9458,"children":9459},{"style":173},[9460],{"type":30,"value":176},{"type":24,"tag":155,"props":9462,"children":9463},{"style":327},[9464],{"type":30,"value":9465},"UseBlazorFrameworkFiles",{"type":24,"tag":155,"props":9467,"children":9468},{"style":173},[9469],{"type":30,"value":516},{"type":24,"tag":155,"props":9471,"children":9472},{"class":157,"line":519},[9473,9477,9481,9486],{"type":24,"tag":155,"props":9474,"children":9475},{"style":479},[9476],{"type":30,"value":9236},{"type":24,"tag":155,"props":9478,"children":9479},{"style":173},[9480],{"type":30,"value":176},{"type":24,"tag":155,"props":9482,"children":9483},{"style":327},[9484],{"type":30,"value":9485},"UseStaticFiles",{"type":24,"tag":155,"props":9487,"children":9488},{"style":173},[9489],{"type":30,"value":516},{"type":24,"tag":155,"props":9491,"children":9492},{"class":157,"line":540},[9493,9497,9501,9506,9510,9514,9519,9523],{"type":24,"tag":155,"props":9494,"children":9495},{"style":479},[9496],{"type":30,"value":9236},{"type":24,"tag":155,"props":9498,"children":9499},{"style":173},[9500],{"type":30,"value":176},{"type":24,"tag":155,"props":9502,"children":9503},{"style":327},[9504],{"type":30,"value":9505},"MapFallbackToFile",{"type":24,"tag":155,"props":9507,"children":9508},{"style":173},[9509],{"type":30,"value":551},{"type":24,"tag":155,"props":9511,"children":9512},{"style":768},[9513],{"type":30,"value":2246},{"type":24,"tag":155,"props":9515,"children":9516},{"style":2239},[9517],{"type":30,"value":9518},"index.html",{"type":24,"tag":155,"props":9520,"children":9521},{"style":768},[9522],{"type":30,"value":2246},{"type":24,"tag":155,"props":9524,"children":9525},{"style":173},[9526],{"type":30,"value":560},{"type":24,"tag":155,"props":9528,"children":9529},{"class":157,"line":563},[9530],{"type":24,"tag":155,"props":9531,"children":9532},{"emptyLinePlaceholder":227},[9533],{"type":30,"value":230},{"type":24,"tag":155,"props":9535,"children":9536},{"class":157,"line":572},[9537,9541,9545,9550],{"type":24,"tag":155,"props":9538,"children":9539},{"style":479},[9540],{"type":30,"value":9236},{"type":24,"tag":155,"props":9542,"children":9543},{"style":173},[9544],{"type":30,"value":176},{"type":24,"tag":155,"props":9546,"children":9547},{"style":327},[9548],{"type":30,"value":9549},"Run",{"type":24,"tag":155,"props":9551,"children":9552},{"style":173},[9553],{"type":30,"value":516},{"type":24,"tag":32,"props":9555,"children":9556},{},[9557],{"type":30,"value":9558},"肝となる部分はレスポンスヘッダを設定している処理ですね。ここで、クロスオリジン分離を宣言しています。",{"type":24,"tag":8969,"props":9560,"children":9562},{"id":9561},"音楽生成のバックグラウンドスレッド化",[9563],{"type":30,"value":9561},{"type":24,"tag":32,"props":9565,"children":9566},{},[9567,9569,9575],{"type":30,"value":9568},"バックグラウンドスレッドで実行すること自体は、普段通り",{"type":24,"tag":46,"props":9570,"children":9572},{"className":9571},[],[9573],{"type":30,"value":9574},"async/await",{"type":30,"value":9576},"で実装することで実現できます。したがって、ここまで設定できれば普段通り非同期処理を実装するだけとなります。",{"type":24,"tag":8969,"props":9578,"children":9580},{"id":9579},"再生処理ではchanneltを利用する",[9581,9583,9588],{"type":30,"value":9582},"再生処理では",{"type":24,"tag":46,"props":9584,"children":9586},{"className":9585},[],[9587],{"type":30,"value":8830},{"type":30,"value":9589},"を利用する",{"type":24,"tag":32,"props":9591,"children":9592},{},[9593,9595,9605,9607,9613],{"type":30,"value":9594},"肝心の再生処理です。",{"type":24,"tag":83,"props":9596,"children":9598},{"content":9597},".NET標準のスレッド間データ受け渡し用コレクション。非同期でProducerとConsumerを接続できる",[9599],{"type":24,"tag":46,"props":9600,"children":9602},{"className":9601},[],[9603],{"type":30,"value":9604},"Channel",{"type":30,"value":9606},"\nを使った\n",{"type":24,"tag":83,"props":9608,"children":9610},{"content":9609},"データを生成する側（Producer）と消費する側（Consumer）を分離し、非同期にデータを受け渡す設計パターン",[9611],{"type":30,"value":9612},"Producer-Consumer パターン",{"type":30,"value":9614},"\nで、バックグラウンド生成しつつストリーミング再生を維持するということをしました。",{"type":24,"tag":9616,"props":9617,"children":9619},"h5",{"id":9618},"そもそもなぜストリーミングを使っているか",[9620],{"type":30,"value":9621},"そもそもなぜストリーミングを使っているか？",{"type":24,"tag":32,"props":9623,"children":9624},{},[9625],{"type":30,"value":9626},"単純な話、楽曲の音声を全て生成してから再生しようとすると、再生が始まるまで時間がかかるからです。\n少し生成→再生→さらに生成→再生というのをスレッドを分けて実現しようというのが主旨となります。",{"type":24,"tag":9616,"props":9628,"children":9630},{"id":9629},"producer側の実装",[9631],{"type":30,"value":9632},"Producer側の実装",{"type":24,"tag":145,"props":9634,"children":9636},{"className":147,"code":9635,"language":149,"meta":8,"style":8},"public (ChannelReader\u003Cbyte[]> Reader, Task GenerationTask) GetSoundsForPlayStreaming(\n    int seekIndex, CancellationToken cancellationToken = default)\n{\n    // UIスレッドでスナップショット取得\n    var tempo = EditingMusic.Value.Settings.Tempo;\n    var trackSnapshots = EditingMusic.Value.Tracks.Select(t => t.Clone()).ToList();\n\n    var channel = Channel.CreateBounded\u003Cbyte[]>(new BoundedChannelOptions(2)\n    {\n        FullMode = BoundedChannelFullMode.Wait\n    });\n\n    // (Producer)バックグラウンドで音声をストリーミング生成する\n    var task = Task.Run(async () =>\n    {\n        try\n        {\n            foreach (var buffer in SoundGenerator.GetSoundsForPlay(seekIndex, tempo, trackSnapshots))\n            {\n                cancellationToken.ThrowIfCancellationRequested();\n                // ここでチャンネルに書き込む Producer -> Consumer\n                await channel.Writer.WriteAsync(buffer, cancellationToken);\n            }\n        }\n        finally\n        {\n            channel.Writer.Complete();\n        }\n    }, cancellationToken);\n\n    return (channel.Reader, task);\n}\n",[9637],{"type":24,"tag":46,"props":9638,"children":9639},{"__ignoreMap":8},[9640,9702,9742,9749,9757,9809,9892,9899,9964,9971,9997,10005,10012,10020,10065,10072,10080,10087,10156,10164,10185,10193,10244,10252,10259,10267,10274,10303,10310,10326,10333,10370],{"type":24,"tag":155,"props":9641,"children":9642},{"class":157,"line":18},[9643,9647,9651,9656,9660,9664,9669,9674,9678,9683,9688,9692,9697],{"type":24,"tag":155,"props":9644,"children":9645},{"style":237},[9646],{"type":30,"value":266},{"type":24,"tag":155,"props":9648,"children":9649},{"style":173},[9650],{"type":30,"value":476},{"type":24,"tag":155,"props":9652,"children":9653},{"style":167},[9654],{"type":30,"value":9655},"ChannelReader",{"type":24,"tag":155,"props":9657,"children":9658},{"style":173},[9659],{"type":30,"value":366},{"type":24,"tag":155,"props":9661,"children":9662},{"style":161},[9663],{"type":30,"value":4202},{"type":24,"tag":155,"props":9665,"children":9666},{"style":173},[9667],{"type":30,"value":9668},"[]>",{"type":24,"tag":155,"props":9670,"children":9671},{"style":327},[9672],{"type":30,"value":9673}," Reader",{"type":24,"tag":155,"props":9675,"children":9676},{"style":173},[9677],{"type":30,"value":396},{"type":24,"tag":155,"props":9679,"children":9680},{"style":167},[9681],{"type":30,"value":9682}," Task",{"type":24,"tag":155,"props":9684,"children":9685},{"style":327},[9686],{"type":30,"value":9687}," GenerationTask",{"type":24,"tag":155,"props":9689,"children":9690},{"style":173},[9691],{"type":30,"value":496},{"type":24,"tag":155,"props":9693,"children":9694},{"style":327},[9695],{"type":30,"value":9696}," GetSoundsForPlayStreaming",{"type":24,"tag":155,"props":9698,"children":9699},{"style":173},[9700],{"type":30,"value":9701},"(\n",{"type":24,"tag":155,"props":9703,"children":9704},{"class":157,"line":189},[9705,9710,9715,9719,9724,9729,9733,9738],{"type":24,"tag":155,"props":9706,"children":9707},{"style":161},[9708],{"type":30,"value":9709},"    int",{"type":24,"tag":155,"props":9711,"children":9712},{"style":327},[9713],{"type":30,"value":9714}," seekIndex",{"type":24,"tag":155,"props":9716,"children":9717},{"style":173},[9718],{"type":30,"value":396},{"type":24,"tag":155,"props":9720,"children":9721},{"style":167},[9722],{"type":30,"value":9723}," CancellationToken",{"type":24,"tag":155,"props":9725,"children":9726},{"style":327},[9727],{"type":30,"value":9728}," cancellationToken",{"type":24,"tag":155,"props":9730,"children":9731},{"style":173},[9732],{"type":30,"value":443},{"type":24,"tag":155,"props":9734,"children":9735},{"style":237},[9736],{"type":30,"value":9737}," default",{"type":24,"tag":155,"props":9739,"children":9740},{"style":173},[9741],{"type":30,"value":453},{"type":24,"tag":155,"props":9743,"children":9744},{"class":157,"line":223},[9745],{"type":24,"tag":155,"props":9746,"children":9747},{"style":173},[9748],{"type":30,"value":300},{"type":24,"tag":155,"props":9750,"children":9751},{"class":157,"line":233},[9752],{"type":24,"tag":155,"props":9753,"children":9754},{"style":1411},[9755],{"type":30,"value":9756},"    // UIスレッドでスナップショット取得\n",{"type":24,"tag":155,"props":9758,"children":9759},{"class":157,"line":252},[9760,9764,9769,9773,9778,9782,9787,9791,9796,9800,9805],{"type":24,"tag":155,"props":9761,"children":9762},{"style":237},[9763],{"type":30,"value":4904},{"type":24,"tag":155,"props":9765,"children":9766},{"style":327},[9767],{"type":30,"value":9768}," tempo",{"type":24,"tag":155,"props":9770,"children":9771},{"style":173},[9772],{"type":30,"value":443},{"type":24,"tag":155,"props":9774,"children":9775},{"style":479},[9776],{"type":30,"value":9777}," EditingMusic",{"type":24,"tag":155,"props":9779,"children":9780},{"style":173},[9781],{"type":30,"value":176},{"type":24,"tag":155,"props":9783,"children":9784},{"style":479},[9785],{"type":30,"value":9786},"Value",{"type":24,"tag":155,"props":9788,"children":9789},{"style":173},[9790],{"type":30,"value":176},{"type":24,"tag":155,"props":9792,"children":9793},{"style":479},[9794],{"type":30,"value":9795},"Settings",{"type":24,"tag":155,"props":9797,"children":9798},{"style":173},[9799],{"type":30,"value":176},{"type":24,"tag":155,"props":9801,"children":9802},{"style":479},[9803],{"type":30,"value":9804},"Tempo",{"type":24,"tag":155,"props":9806,"children":9807},{"style":173},[9808],{"type":30,"value":186},{"type":24,"tag":155,"props":9810,"children":9811},{"class":157,"line":260},[9812,9816,9821,9825,9829,9833,9837,9841,9845,9849,9853,9857,9862,9866,9871,9875,9879,9884,9888],{"type":24,"tag":155,"props":9813,"children":9814},{"style":237},[9815],{"type":30,"value":4904},{"type":24,"tag":155,"props":9817,"children":9818},{"style":327},[9819],{"type":30,"value":9820}," trackSnapshots",{"type":24,"tag":155,"props":9822,"children":9823},{"style":173},[9824],{"type":30,"value":443},{"type":24,"tag":155,"props":9826,"children":9827},{"style":479},[9828],{"type":30,"value":9777},{"type":24,"tag":155,"props":9830,"children":9831},{"style":173},[9832],{"type":30,"value":176},{"type":24,"tag":155,"props":9834,"children":9835},{"style":479},[9836],{"type":30,"value":9786},{"type":24,"tag":155,"props":9838,"children":9839},{"style":173},[9840],{"type":30,"value":176},{"type":24,"tag":155,"props":9842,"children":9843},{"style":479},[9844],{"type":30,"value":4390},{"type":24,"tag":155,"props":9846,"children":9847},{"style":173},[9848],{"type":30,"value":176},{"type":24,"tag":155,"props":9850,"children":9851},{"style":327},[9852],{"type":30,"value":5005},{"type":24,"tag":155,"props":9854,"children":9855},{"style":173},[9856],{"type":30,"value":551},{"type":24,"tag":155,"props":9858,"children":9859},{"style":327},[9860],{"type":30,"value":9861},"t",{"type":24,"tag":155,"props":9863,"children":9864},{"style":237},[9865],{"type":30,"value":811},{"type":24,"tag":155,"props":9867,"children":9868},{"style":479},[9869],{"type":30,"value":9870}," t",{"type":24,"tag":155,"props":9872,"children":9873},{"style":173},[9874],{"type":30,"value":176},{"type":24,"tag":155,"props":9876,"children":9877},{"style":327},[9878],{"type":30,"value":2071},{"type":24,"tag":155,"props":9880,"children":9881},{"style":173},[9882],{"type":30,"value":9883},"()).",{"type":24,"tag":155,"props":9885,"children":9886},{"style":327},[9887],{"type":30,"value":7617},{"type":24,"tag":155,"props":9889,"children":9890},{"style":173},[9891],{"type":30,"value":516},{"type":24,"tag":155,"props":9893,"children":9894},{"class":157,"line":294},[9895],{"type":24,"tag":155,"props":9896,"children":9897},{"emptyLinePlaceholder":227},[9898],{"type":30,"value":230},{"type":24,"tag":155,"props":9900,"children":9901},{"class":157,"line":303},[9902,9906,9911,9915,9920,9924,9929,9933,9937,9942,9947,9952,9956,9960],{"type":24,"tag":155,"props":9903,"children":9904},{"style":237},[9905],{"type":30,"value":4904},{"type":24,"tag":155,"props":9907,"children":9908},{"style":327},[9909],{"type":30,"value":9910}," channel",{"type":24,"tag":155,"props":9912,"children":9913},{"style":173},[9914],{"type":30,"value":443},{"type":24,"tag":155,"props":9916,"children":9917},{"style":479},[9918],{"type":30,"value":9919}," Channel",{"type":24,"tag":155,"props":9921,"children":9922},{"style":173},[9923],{"type":30,"value":176},{"type":24,"tag":155,"props":9925,"children":9926},{"style":327},[9927],{"type":30,"value":9928},"CreateBounded",{"type":24,"tag":155,"props":9930,"children":9931},{"style":173},[9932],{"type":30,"value":366},{"type":24,"tag":155,"props":9934,"children":9935},{"style":161},[9936],{"type":30,"value":4202},{"type":24,"tag":155,"props":9938,"children":9939},{"style":173},[9940],{"type":30,"value":9941},"[]>(",{"type":24,"tag":155,"props":9943,"children":9944},{"style":237},[9945],{"type":30,"value":9946},"new",{"type":24,"tag":155,"props":9948,"children":9949},{"style":167},[9950],{"type":30,"value":9951}," BoundedChannelOptions",{"type":24,"tag":155,"props":9953,"children":9954},{"style":173},[9955],{"type":30,"value":551},{"type":24,"tag":155,"props":9957,"children":9958},{"style":3095},[9959],{"type":30,"value":3268},{"type":24,"tag":155,"props":9961,"children":9962},{"style":173},[9963],{"type":30,"value":453},{"type":24,"tag":155,"props":9965,"children":9966},{"class":157,"line":337},[9967],{"type":24,"tag":155,"props":9968,"children":9969},{"style":173},[9970],{"type":30,"value":462},{"type":24,"tag":155,"props":9972,"children":9973},{"class":157,"line":345},[9974,9979,9983,9988,9992],{"type":24,"tag":155,"props":9975,"children":9976},{"style":479},[9977],{"type":30,"value":9978},"        FullMode",{"type":24,"tag":155,"props":9980,"children":9981},{"style":173},[9982],{"type":30,"value":443},{"type":24,"tag":155,"props":9984,"children":9985},{"style":479},[9986],{"type":30,"value":9987}," BoundedChannelFullMode",{"type":24,"tag":155,"props":9989,"children":9990},{"style":173},[9991],{"type":30,"value":176},{"type":24,"tag":155,"props":9993,"children":9994},{"style":479},[9995],{"type":30,"value":9996},"Wait\n",{"type":24,"tag":155,"props":9998,"children":9999},{"class":157,"line":456},[10000],{"type":24,"tag":155,"props":10001,"children":10002},{"style":173},[10003],{"type":30,"value":10004},"    });\n",{"type":24,"tag":155,"props":10006,"children":10007},{"class":157,"line":465},[10008],{"type":24,"tag":155,"props":10009,"children":10010},{"emptyLinePlaceholder":227},[10011],{"type":30,"value":230},{"type":24,"tag":155,"props":10013,"children":10014},{"class":157,"line":519},[10015],{"type":24,"tag":155,"props":10016,"children":10017},{"style":1411},[10018],{"type":30,"value":10019},"    // (Producer)バックグラウンドで音声をストリーミング生成する\n",{"type":24,"tag":155,"props":10021,"children":10022},{"class":157,"line":540},[10023,10027,10032,10036,10040,10044,10048,10052,10056,10061],{"type":24,"tag":155,"props":10024,"children":10025},{"style":237},[10026],{"type":30,"value":4904},{"type":24,"tag":155,"props":10028,"children":10029},{"style":327},[10030],{"type":30,"value":10031}," task",{"type":24,"tag":155,"props":10033,"children":10034},{"style":173},[10035],{"type":30,"value":443},{"type":24,"tag":155,"props":10037,"children":10038},{"style":479},[10039],{"type":30,"value":9682},{"type":24,"tag":155,"props":10041,"children":10042},{"style":173},[10043],{"type":30,"value":176},{"type":24,"tag":155,"props":10045,"children":10046},{"style":327},[10047],{"type":30,"value":9549},{"type":24,"tag":155,"props":10049,"children":10050},{"style":173},[10051],{"type":30,"value":551},{"type":24,"tag":155,"props":10053,"children":10054},{"style":237},[10055],{"type":30,"value":9254},{"type":24,"tag":155,"props":10057,"children":10058},{"style":173},[10059],{"type":30,"value":10060}," ()",{"type":24,"tag":155,"props":10062,"children":10063},{"style":237},[10064],{"type":30,"value":2183},{"type":24,"tag":155,"props":10066,"children":10067},{"class":157,"line":563},[10068],{"type":24,"tag":155,"props":10069,"children":10070},{"style":173},[10071],{"type":30,"value":462},{"type":24,"tag":155,"props":10073,"children":10074},{"class":157,"line":572},[10075],{"type":24,"tag":155,"props":10076,"children":10077},{"style":161},[10078],{"type":30,"value":10079},"        try\n",{"type":24,"tag":155,"props":10081,"children":10082},{"class":157,"line":580},[10083],{"type":24,"tag":155,"props":10084,"children":10085},{"style":173},[10086],{"type":30,"value":7748},{"type":24,"tag":155,"props":10088,"children":10089},{"class":157,"line":614},[10090,10095,10099,10103,10108,10112,10117,10121,10126,10130,10135,10139,10143,10147,10151],{"type":24,"tag":155,"props":10091,"children":10092},{"style":161},[10093],{"type":30,"value":10094},"            foreach",{"type":24,"tag":155,"props":10096,"children":10097},{"style":173},[10098],{"type":30,"value":476},{"type":24,"tag":155,"props":10100,"children":10101},{"style":237},[10102],{"type":30,"value":2019},{"type":24,"tag":155,"props":10104,"children":10105},{"style":327},[10106],{"type":30,"value":10107}," buffer",{"type":24,"tag":155,"props":10109,"children":10110},{"style":161},[10111],{"type":30,"value":7712},{"type":24,"tag":155,"props":10113,"children":10114},{"style":479},[10115],{"type":30,"value":10116}," SoundGenerator",{"type":24,"tag":155,"props":10118,"children":10119},{"style":173},[10120],{"type":30,"value":176},{"type":24,"tag":155,"props":10122,"children":10123},{"style":327},[10124],{"type":30,"value":10125},"GetSoundsForPlay",{"type":24,"tag":155,"props":10127,"children":10128},{"style":173},[10129],{"type":30,"value":551},{"type":24,"tag":155,"props":10131,"children":10132},{"style":479},[10133],{"type":30,"value":10134},"seekIndex",{"type":24,"tag":155,"props":10136,"children":10137},{"style":173},[10138],{"type":30,"value":396},{"type":24,"tag":155,"props":10140,"children":10141},{"style":479},[10142],{"type":30,"value":9768},{"type":24,"tag":155,"props":10144,"children":10145},{"style":173},[10146],{"type":30,"value":396},{"type":24,"tag":155,"props":10148,"children":10149},{"style":479},[10150],{"type":30,"value":9820},{"type":24,"tag":155,"props":10152,"children":10153},{"style":173},[10154],{"type":30,"value":10155},"))\n",{"type":24,"tag":155,"props":10157,"children":10158},{"class":157,"line":622},[10159],{"type":24,"tag":155,"props":10160,"children":10161},{"style":173},[10162],{"type":30,"value":10163},"            {\n",{"type":24,"tag":155,"props":10165,"children":10166},{"class":157,"line":680},[10167,10172,10176,10181],{"type":24,"tag":155,"props":10168,"children":10169},{"style":479},[10170],{"type":30,"value":10171},"                cancellationToken",{"type":24,"tag":155,"props":10173,"children":10174},{"style":173},[10175],{"type":30,"value":176},{"type":24,"tag":155,"props":10177,"children":10178},{"style":327},[10179],{"type":30,"value":10180},"ThrowIfCancellationRequested",{"type":24,"tag":155,"props":10182,"children":10183},{"style":173},[10184],{"type":30,"value":516},{"type":24,"tag":155,"props":10186,"children":10187},{"class":157,"line":688},[10188],{"type":24,"tag":155,"props":10189,"children":10190},{"style":1411},[10191],{"type":30,"value":10192},"                // ここでチャンネルに書き込む Producer -> Consumer\n",{"type":24,"tag":155,"props":10194,"children":10195},{"class":157,"line":7010},[10196,10201,10205,10209,10214,10218,10223,10227,10232,10236,10240],{"type":24,"tag":155,"props":10197,"children":10198},{"style":237},[10199],{"type":30,"value":10200},"                await",{"type":24,"tag":155,"props":10202,"children":10203},{"style":479},[10204],{"type":30,"value":9910},{"type":24,"tag":155,"props":10206,"children":10207},{"style":173},[10208],{"type":30,"value":176},{"type":24,"tag":155,"props":10210,"children":10211},{"style":479},[10212],{"type":30,"value":10213},"Writer",{"type":24,"tag":155,"props":10215,"children":10216},{"style":173},[10217],{"type":30,"value":176},{"type":24,"tag":155,"props":10219,"children":10220},{"style":327},[10221],{"type":30,"value":10222},"WriteAsync",{"type":24,"tag":155,"props":10224,"children":10225},{"style":173},[10226],{"type":30,"value":551},{"type":24,"tag":155,"props":10228,"children":10229},{"style":479},[10230],{"type":30,"value":10231},"buffer",{"type":24,"tag":155,"props":10233,"children":10234},{"style":173},[10235],{"type":30,"value":396},{"type":24,"tag":155,"props":10237,"children":10238},{"style":479},[10239],{"type":30,"value":9728},{"type":24,"tag":155,"props":10241,"children":10242},{"style":173},[10243],{"type":30,"value":560},{"type":24,"tag":155,"props":10245,"children":10246},{"class":157,"line":7023},[10247],{"type":24,"tag":155,"props":10248,"children":10249},{"style":173},[10250],{"type":30,"value":10251},"            }\n",{"type":24,"tag":155,"props":10253,"children":10254},{"class":157,"line":7048},[10255],{"type":24,"tag":155,"props":10256,"children":10257},{"style":173},[10258],{"type":30,"value":7785},{"type":24,"tag":155,"props":10260,"children":10261},{"class":157,"line":7056},[10262],{"type":24,"tag":155,"props":10263,"children":10264},{"style":161},[10265],{"type":30,"value":10266},"        finally\n",{"type":24,"tag":155,"props":10268,"children":10269},{"class":157,"line":7064},[10270],{"type":24,"tag":155,"props":10271,"children":10272},{"style":173},[10273],{"type":30,"value":7748},{"type":24,"tag":155,"props":10275,"children":10276},{"class":157,"line":7085},[10277,10282,10286,10290,10294,10299],{"type":24,"tag":155,"props":10278,"children":10279},{"style":479},[10280],{"type":30,"value":10281},"            channel",{"type":24,"tag":155,"props":10283,"children":10284},{"style":173},[10285],{"type":30,"value":176},{"type":24,"tag":155,"props":10287,"children":10288},{"style":479},[10289],{"type":30,"value":10213},{"type":24,"tag":155,"props":10291,"children":10292},{"style":173},[10293],{"type":30,"value":176},{"type":24,"tag":155,"props":10295,"children":10296},{"style":327},[10297],{"type":30,"value":10298},"Complete",{"type":24,"tag":155,"props":10300,"children":10301},{"style":173},[10302],{"type":30,"value":516},{"type":24,"tag":155,"props":10304,"children":10305},{"class":157,"line":7093},[10306],{"type":24,"tag":155,"props":10307,"children":10308},{"style":173},[10309],{"type":30,"value":7785},{"type":24,"tag":155,"props":10311,"children":10312},{"class":157,"line":7127},[10313,10318,10322],{"type":24,"tag":155,"props":10314,"children":10315},{"style":173},[10316],{"type":30,"value":10317},"    },",{"type":24,"tag":155,"props":10319,"children":10320},{"style":479},[10321],{"type":30,"value":9728},{"type":24,"tag":155,"props":10323,"children":10324},{"style":173},[10325],{"type":30,"value":560},{"type":24,"tag":155,"props":10327,"children":10328},{"class":157,"line":7161},[10329],{"type":24,"tag":155,"props":10330,"children":10331},{"emptyLinePlaceholder":227},[10332],{"type":30,"value":230},{"type":24,"tag":155,"props":10334,"children":10335},{"class":157,"line":7181},[10336,10340,10344,10349,10353,10358,10362,10366],{"type":24,"tag":155,"props":10337,"children":10338},{"style":161},[10339],{"type":30,"value":5045},{"type":24,"tag":155,"props":10341,"children":10342},{"style":173},[10343],{"type":30,"value":476},{"type":24,"tag":155,"props":10345,"children":10346},{"style":479},[10347],{"type":30,"value":10348},"channel",{"type":24,"tag":155,"props":10350,"children":10351},{"style":173},[10352],{"type":30,"value":176},{"type":24,"tag":155,"props":10354,"children":10355},{"style":479},[10356],{"type":30,"value":10357},"Reader",{"type":24,"tag":155,"props":10359,"children":10360},{"style":173},[10361],{"type":30,"value":396},{"type":24,"tag":155,"props":10363,"children":10364},{"style":479},[10365],{"type":30,"value":10031},{"type":24,"tag":155,"props":10367,"children":10368},{"style":173},[10369],{"type":30,"value":560},{"type":24,"tag":155,"props":10371,"children":10372},{"class":157,"line":7209},[10373],{"type":24,"tag":155,"props":10374,"children":10375},{"style":173},[10376],{"type":30,"value":694},{"type":24,"tag":9616,"props":10378,"children":10380},{"id":10379},"consumer側の実装",[10381],{"type":30,"value":10382},"Consumer側の実装",{"type":24,"tag":145,"props":10384,"children":10386},{"className":147,"code":10385,"language":149,"meta":8,"style":8},"var (reader, generationTask) = Store.GetSoundsForPlayStreaming(Store.SeekIndex, cancellationToken);\n\n// reader経由でチャンネルの内容を取得する\nawait foreach (var sound in reader.ReadAllAsync(cancellationToken))\n{\n    await App.Instance.PlaySoundAsync(sound);\n}\n\nawait generationTask;\n",[10387],{"type":24,"tag":46,"props":10388,"children":10389},{"__ignoreMap":8},[10390,10467,10474,10482,10539,10546,10593,10600,10607],{"type":24,"tag":155,"props":10391,"children":10392},{"class":157,"line":18},[10393,10397,10401,10406,10410,10415,10419,10423,10428,10432,10437,10441,10446,10450,10455,10459,10463],{"type":24,"tag":155,"props":10394,"children":10395},{"style":237},[10396],{"type":30,"value":2019},{"type":24,"tag":155,"props":10398,"children":10399},{"style":173},[10400],{"type":30,"value":476},{"type":24,"tag":155,"props":10402,"children":10403},{"style":327},[10404],{"type":30,"value":10405},"reader",{"type":24,"tag":155,"props":10407,"children":10408},{"style":173},[10409],{"type":30,"value":396},{"type":24,"tag":155,"props":10411,"children":10412},{"style":327},[10413],{"type":30,"value":10414}," generationTask",{"type":24,"tag":155,"props":10416,"children":10417},{"style":173},[10418],{"type":30,"value":496},{"type":24,"tag":155,"props":10420,"children":10421},{"style":173},[10422],{"type":30,"value":443},{"type":24,"tag":155,"props":10424,"children":10425},{"style":479},[10426],{"type":30,"value":10427}," Store",{"type":24,"tag":155,"props":10429,"children":10430},{"style":173},[10431],{"type":30,"value":176},{"type":24,"tag":155,"props":10433,"children":10434},{"style":327},[10435],{"type":30,"value":10436},"GetSoundsForPlayStreaming",{"type":24,"tag":155,"props":10438,"children":10439},{"style":173},[10440],{"type":30,"value":551},{"type":24,"tag":155,"props":10442,"children":10443},{"style":479},[10444],{"type":30,"value":10445},"Store",{"type":24,"tag":155,"props":10447,"children":10448},{"style":173},[10449],{"type":30,"value":176},{"type":24,"tag":155,"props":10451,"children":10452},{"style":479},[10453],{"type":30,"value":10454},"SeekIndex",{"type":24,"tag":155,"props":10456,"children":10457},{"style":173},[10458],{"type":30,"value":396},{"type":24,"tag":155,"props":10460,"children":10461},{"style":479},[10462],{"type":30,"value":9728},{"type":24,"tag":155,"props":10464,"children":10465},{"style":173},[10466],{"type":30,"value":560},{"type":24,"tag":155,"props":10468,"children":10469},{"class":157,"line":189},[10470],{"type":24,"tag":155,"props":10471,"children":10472},{"emptyLinePlaceholder":227},[10473],{"type":30,"value":230},{"type":24,"tag":155,"props":10475,"children":10476},{"class":157,"line":223},[10477],{"type":24,"tag":155,"props":10478,"children":10479},{"style":1411},[10480],{"type":30,"value":10481},"// reader経由でチャンネルの内容を取得する\n",{"type":24,"tag":155,"props":10483,"children":10484},{"class":157,"line":233},[10485,10490,10495,10499,10503,10508,10512,10517,10521,10526,10530,10535],{"type":24,"tag":155,"props":10486,"children":10487},{"style":237},[10488],{"type":30,"value":10489},"await",{"type":24,"tag":155,"props":10491,"children":10492},{"style":161},[10493],{"type":30,"value":10494}," foreach",{"type":24,"tag":155,"props":10496,"children":10497},{"style":173},[10498],{"type":30,"value":476},{"type":24,"tag":155,"props":10500,"children":10501},{"style":237},[10502],{"type":30,"value":2019},{"type":24,"tag":155,"props":10504,"children":10505},{"style":327},[10506],{"type":30,"value":10507}," sound",{"type":24,"tag":155,"props":10509,"children":10510},{"style":161},[10511],{"type":30,"value":7712},{"type":24,"tag":155,"props":10513,"children":10514},{"style":479},[10515],{"type":30,"value":10516}," reader",{"type":24,"tag":155,"props":10518,"children":10519},{"style":173},[10520],{"type":30,"value":176},{"type":24,"tag":155,"props":10522,"children":10523},{"style":327},[10524],{"type":30,"value":10525},"ReadAllAsync",{"type":24,"tag":155,"props":10527,"children":10528},{"style":173},[10529],{"type":30,"value":551},{"type":24,"tag":155,"props":10531,"children":10532},{"style":479},[10533],{"type":30,"value":10534},"cancellationToken",{"type":24,"tag":155,"props":10536,"children":10537},{"style":173},[10538],{"type":30,"value":10155},{"type":24,"tag":155,"props":10540,"children":10541},{"class":157,"line":252},[10542],{"type":24,"tag":155,"props":10543,"children":10544},{"style":173},[10545],{"type":30,"value":300},{"type":24,"tag":155,"props":10547,"children":10548},{"class":157,"line":260},[10549,10553,10558,10562,10567,10571,10576,10580,10585,10589],{"type":24,"tag":155,"props":10550,"children":10551},{"style":237},[10552],{"type":30,"value":9426},{"type":24,"tag":155,"props":10554,"children":10555},{"style":479},[10556],{"type":30,"value":10557}," App",{"type":24,"tag":155,"props":10559,"children":10560},{"style":173},[10561],{"type":30,"value":176},{"type":24,"tag":155,"props":10563,"children":10564},{"style":479},[10565],{"type":30,"value":10566},"Instance",{"type":24,"tag":155,"props":10568,"children":10569},{"style":173},[10570],{"type":30,"value":176},{"type":24,"tag":155,"props":10572,"children":10573},{"style":327},[10574],{"type":30,"value":10575},"PlaySoundAsync",{"type":24,"tag":155,"props":10577,"children":10578},{"style":173},[10579],{"type":30,"value":551},{"type":24,"tag":155,"props":10581,"children":10582},{"style":479},[10583],{"type":30,"value":10584},"sound",{"type":24,"tag":155,"props":10586,"children":10587},{"style":173},[10588],{"type":30,"value":496},{"type":24,"tag":155,"props":10590,"children":10591},{"style":8917},[10592],{"type":30,"value":186},{"type":24,"tag":155,"props":10594,"children":10595},{"class":157,"line":294},[10596],{"type":24,"tag":155,"props":10597,"children":10598},{"style":173},[10599],{"type":30,"value":694},{"type":24,"tag":155,"props":10601,"children":10602},{"class":157,"line":303},[10603],{"type":24,"tag":155,"props":10604,"children":10605},{"emptyLinePlaceholder":227},[10606],{"type":30,"value":230},{"type":24,"tag":155,"props":10608,"children":10609},{"class":157,"line":337},[10610,10614,10618],{"type":24,"tag":155,"props":10611,"children":10612},{"style":237},[10613],{"type":30,"value":10489},{"type":24,"tag":155,"props":10615,"children":10616},{"style":479},[10617],{"type":30,"value":10414},{"type":24,"tag":155,"props":10619,"children":10620},{"style":173},[10621],{"type":30,"value":186},{"type":24,"tag":32,"props":10623,"children":10624},{},[10625],{"type":30,"value":10626},"このように、簡単にバックグラウンドスレッドからUIスレッドへのストリーミングを実現することができます。この機能自体は.NET標準なのでBlazor以外でも使うことができます。",{"type":24,"tag":8969,"props":10628,"children":10630},{"id":10629},"別の不具合が発生autosaver-のスレッド安全性修正",[10631],{"type":30,"value":10632},"別の不具合が発生！AutoSaver のスレッド安全性修正",{"type":24,"tag":32,"props":10634,"children":10635},{},[10636],{"type":30,"value":10637},"再生処理は簡単に改善できましたが、PICOMに搭載していた自動セーブ機能で用いていたタイマーが壊れる不具合が発生しました。",{"type":24,"tag":32,"props":10639,"children":10640},{},[10641,10643,10648,10650,10655],{"type":30,"value":10642},"原因は、",{"type":24,"tag":46,"props":10644,"children":10646},{"className":10645},[],[10647],{"type":30,"value":8807},{"type":30,"value":10649}," により ",{"type":24,"tag":46,"props":10651,"children":10653},{"className":10652},[],[10654],{"type":30,"value":8838},{"type":30,"value":10656}," のコールバックが実際のバックグラウンドスレッドで実行されるようになり、PropertyChanged イベントチェーンがバックグラウンドから発火して Blazor のレンダリング競合を引き起こしたというものです。UIの更新はUIスレッドで実行しないといけないという制約の影響となります。",{"type":24,"tag":32,"props":10658,"children":10659},{},[10660,10662,10667,10668,10677],{"type":30,"value":10661},"修正方法は、 ",{"type":24,"tag":46,"props":10663,"children":10665},{"className":10664},[],[10666],{"type":30,"value":8838},{"type":30,"value":8840},{"type":24,"tag":83,"props":10669,"children":10671},{"content":10670},".NET 6で追加された非同期対応タイマー。awaitで次のティックを待てるため、asyncメソッド内で安全に使える",[10672],{"type":24,"tag":46,"props":10673,"children":10675},{"className":10674},[],[10676],{"type":30,"value":8846},{"type":30,"value":10678}," に変更するだけです。",{"type":24,"tag":145,"props":10680,"children":10682},{"className":147,"code":10681,"language":149,"meta":8,"style":8},"// Before: System.Timers.Timer（コールバックがスレッドプールで実行される）\nprivate async void TimerCallback(object sender, ElapsedEventArgs e)\n{\n    await _saveAction.Invoke();\n}\n\n// After: PeriodicTimer（async コンテキストで安全に実行）\nprivate async Task RunAsync(CancellationToken cancellationToken)\n{\n    while (await _timer.WaitForNextTickAsync(cancellationToken))\n    {\n        await _saveAction.Invoke();\n    }\n}\n",[10683],{"type":24,"tag":46,"props":10684,"children":10685},{"__ignoreMap":8},[10686,10694,10744,10751,10775,10782,10789,10797,10834,10841,10882,10889,10913,10920],{"type":24,"tag":155,"props":10687,"children":10688},{"class":157,"line":18},[10689],{"type":24,"tag":155,"props":10690,"children":10691},{"style":1411},[10692],{"type":30,"value":10693},"// Before: System.Timers.Timer（コールバックがスレッドプールで実行される）\n",{"type":24,"tag":155,"props":10695,"children":10696},{"class":157,"line":189},[10697,10701,10706,10710,10715,10719,10723,10727,10731,10736,10740],{"type":24,"tag":155,"props":10698,"children":10699},{"style":237},[10700],{"type":30,"value":1645},{"type":24,"tag":155,"props":10702,"children":10703},{"style":237},[10704],{"type":30,"value":10705}," async",{"type":24,"tag":155,"props":10707,"children":10708},{"style":161},[10709],{"type":30,"value":356},{"type":24,"tag":155,"props":10711,"children":10712},{"style":327},[10713],{"type":30,"value":10714}," TimerCallback",{"type":24,"tag":155,"props":10716,"children":10717},{"style":173},[10718],{"type":30,"value":551},{"type":24,"tag":155,"props":10720,"children":10721},{"style":161},[10722],{"type":30,"value":1662},{"type":24,"tag":155,"props":10724,"children":10725},{"style":327},[10726],{"type":30,"value":1667},{"type":24,"tag":155,"props":10728,"children":10729},{"style":173},[10730],{"type":30,"value":396},{"type":24,"tag":155,"props":10732,"children":10733},{"style":167},[10734],{"type":30,"value":10735}," ElapsedEventArgs",{"type":24,"tag":155,"props":10737,"children":10738},{"style":327},[10739],{"type":30,"value":1680},{"type":24,"tag":155,"props":10741,"children":10742},{"style":173},[10743],{"type":30,"value":453},{"type":24,"tag":155,"props":10745,"children":10746},{"class":157,"line":223},[10747],{"type":24,"tag":155,"props":10748,"children":10749},{"style":173},[10750],{"type":30,"value":300},{"type":24,"tag":155,"props":10752,"children":10753},{"class":157,"line":233},[10754,10758,10763,10767,10771],{"type":24,"tag":155,"props":10755,"children":10756},{"style":237},[10757],{"type":30,"value":9426},{"type":24,"tag":155,"props":10759,"children":10760},{"style":479},[10761],{"type":30,"value":10762}," _saveAction",{"type":24,"tag":155,"props":10764,"children":10765},{"style":173},[10766],{"type":30,"value":176},{"type":24,"tag":155,"props":10768,"children":10769},{"style":327},[10770],{"type":30,"value":641},{"type":24,"tag":155,"props":10772,"children":10773},{"style":173},[10774],{"type":30,"value":516},{"type":24,"tag":155,"props":10776,"children":10777},{"class":157,"line":252},[10778],{"type":24,"tag":155,"props":10779,"children":10780},{"style":173},[10781],{"type":30,"value":694},{"type":24,"tag":155,"props":10783,"children":10784},{"class":157,"line":260},[10785],{"type":24,"tag":155,"props":10786,"children":10787},{"emptyLinePlaceholder":227},[10788],{"type":30,"value":230},{"type":24,"tag":155,"props":10790,"children":10791},{"class":157,"line":294},[10792],{"type":24,"tag":155,"props":10793,"children":10794},{"style":1411},[10795],{"type":30,"value":10796},"// After: PeriodicTimer（async コンテキストで安全に実行）\n",{"type":24,"tag":155,"props":10798,"children":10799},{"class":157,"line":303},[10800,10804,10808,10812,10817,10821,10826,10830],{"type":24,"tag":155,"props":10801,"children":10802},{"style":237},[10803],{"type":30,"value":1645},{"type":24,"tag":155,"props":10805,"children":10806},{"style":237},[10807],{"type":30,"value":10705},{"type":24,"tag":155,"props":10809,"children":10810},{"style":167},[10811],{"type":30,"value":9682},{"type":24,"tag":155,"props":10813,"children":10814},{"style":327},[10815],{"type":30,"value":10816}," RunAsync",{"type":24,"tag":155,"props":10818,"children":10819},{"style":173},[10820],{"type":30,"value":551},{"type":24,"tag":155,"props":10822,"children":10823},{"style":167},[10824],{"type":30,"value":10825},"CancellationToken",{"type":24,"tag":155,"props":10827,"children":10828},{"style":327},[10829],{"type":30,"value":9728},{"type":24,"tag":155,"props":10831,"children":10832},{"style":173},[10833],{"type":30,"value":453},{"type":24,"tag":155,"props":10835,"children":10836},{"class":157,"line":337},[10837],{"type":24,"tag":155,"props":10838,"children":10839},{"style":173},[10840],{"type":30,"value":300},{"type":24,"tag":155,"props":10842,"children":10843},{"class":157,"line":345},[10844,10848,10852,10856,10861,10865,10870,10874,10878],{"type":24,"tag":155,"props":10845,"children":10846},{"style":161},[10847],{"type":30,"value":7553},{"type":24,"tag":155,"props":10849,"children":10850},{"style":173},[10851],{"type":30,"value":476},{"type":24,"tag":155,"props":10853,"children":10854},{"style":237},[10855],{"type":30,"value":10489},{"type":24,"tag":155,"props":10857,"children":10858},{"style":479},[10859],{"type":30,"value":10860}," _timer",{"type":24,"tag":155,"props":10862,"children":10863},{"style":173},[10864],{"type":30,"value":176},{"type":24,"tag":155,"props":10866,"children":10867},{"style":327},[10868],{"type":30,"value":10869},"WaitForNextTickAsync",{"type":24,"tag":155,"props":10871,"children":10872},{"style":173},[10873],{"type":30,"value":551},{"type":24,"tag":155,"props":10875,"children":10876},{"style":479},[10877],{"type":30,"value":10534},{"type":24,"tag":155,"props":10879,"children":10880},{"style":173},[10881],{"type":30,"value":10155},{"type":24,"tag":155,"props":10883,"children":10884},{"class":157,"line":456},[10885],{"type":24,"tag":155,"props":10886,"children":10887},{"style":173},[10888],{"type":30,"value":462},{"type":24,"tag":155,"props":10890,"children":10891},{"class":157,"line":465},[10892,10897,10901,10905,10909],{"type":24,"tag":155,"props":10893,"children":10894},{"style":237},[10895],{"type":30,"value":10896},"        await",{"type":24,"tag":155,"props":10898,"children":10899},{"style":479},[10900],{"type":30,"value":10762},{"type":24,"tag":155,"props":10902,"children":10903},{"style":173},[10904],{"type":30,"value":176},{"type":24,"tag":155,"props":10906,"children":10907},{"style":327},[10908],{"type":30,"value":641},{"type":24,"tag":155,"props":10910,"children":10911},{"style":173},[10912],{"type":30,"value":516},{"type":24,"tag":155,"props":10914,"children":10915},{"class":157,"line":519},[10916],{"type":24,"tag":155,"props":10917,"children":10918},{"style":173},[10919],{"type":30,"value":569},{"type":24,"tag":155,"props":10921,"children":10922},{"class":157,"line":540},[10923],{"type":24,"tag":155,"props":10924,"children":10925},{"style":173},[10926],{"type":30,"value":694},{"type":24,"tag":25,"props":10928,"children":10930},{"id":10929},"wasmenablethreadsを有効化したメリットデメリット",[10931],{"type":30,"value":10932},"WasmEnableThreadsを有効化したメリット・デメリット",{"type":24,"tag":32,"props":10934,"children":10935},{},[10936],{"type":30,"value":10937},"何よりも動作が圧倒的に早くなったという点があります。これまでバックグラウンドで音声を生成している際にUIの動作が明らかにカクついていたのですが、これが完全に改善されました。このおかげでUIの描画やデザインを考える際にビジネスロジックのリソースなどを気にせずに実装できるようになりました。",{"type":24,"tag":32,"props":10939,"children":10940},{},[10941],{"type":30,"value":10942},"デメリットは今のところ大きくは感じていませんが、まだ歴史の浅い機能ということや、マルチスレッド化によってプログラムが複雑化するという問題はあると思います。コードの秩序を保つためにもどのような仕組みかは文書化しておくなど開発工程的な部分で工夫が必要だと思います（今回の記事もこれが目的です）。",{"type":24,"tag":25,"props":10944,"children":10945},{"id":2679},[10946],{"type":30,"value":2679},{"type":24,"tag":917,"props":10948,"children":10949},{},[10950,10968,10978,10988],{"type":24,"tag":921,"props":10951,"children":10952},{},[10953,10954,10959,10961,10966],{"type":30,"value":8817},{"type":24,"tag":46,"props":10955,"children":10957},{"className":10956},[],[10958],{"type":30,"value":8807},{"type":30,"value":10960}," を有効化すれば、",{"type":24,"tag":46,"props":10962,"children":10964},{"className":10963},[],[10965],{"type":30,"value":8942},{"type":30,"value":10967}," で本物のバックグラウンドスレッドが使える",{"type":24,"tag":921,"props":10969,"children":10970},{},[10971,10976],{"type":24,"tag":46,"props":10972,"children":10974},{"className":10973},[],[10975],{"type":30,"value":8965},{"type":30,"value":10977}," を使うため COOP/COEP ヘッダーが必須。開発環境では ASP.NET Core ホストプロジェクトでミドルウェアから設定するのが手軽",{"type":24,"tag":921,"props":10979,"children":10980},{},[10981,10986],{"type":24,"tag":46,"props":10982,"children":10984},{"className":10983},[],[10985],{"type":30,"value":8830},{"type":30,"value":10987}," の Producer-Consumer パターンで、バックグラウンド生成とストリーミング再生を自然に分離できる",{"type":24,"tag":921,"props":10989,"children":10990},{},[10991,10996,10998,11003,11005,11010],{"type":24,"tag":46,"props":10992,"children":10994},{"className":10993},[],[10995],{"type":30,"value":8838},{"type":30,"value":10997}," はスレッドプールでコールバックが走るため、",{"type":24,"tag":46,"props":10999,"children":11001},{"className":11000},[],[11002],{"type":30,"value":8807},{"type":30,"value":11004}," 有効化後に Blazor のレンダリング競合を起こす。",{"type":24,"tag":46,"props":11006,"children":11008},{"className":11007},[],[11009],{"type":30,"value":8846},{"type":30,"value":11011}," に置き換えれば async コンテキストで安全に動作する",{"type":24,"tag":2721,"props":11013,"children":11014},{},[11015],{"type":30,"value":2725},{"title":8,"searchDepth":189,"depth":189,"links":11017},[11018,11019,11020,11024,11025],{"id":8785,"depth":189,"text":8785},{"id":8851,"depth":189,"text":8851},{"id":8874,"depth":189,"text":8877,"children":11021},[11022,11023],{"id":8947,"depth":223,"text":8950},{"id":9075,"depth":223,"text":9078},{"id":10929,"depth":189,"text":10932},{"id":2679,"depth":189,"text":2679},"content:articles:tech:blazor:blazor-wasm-multithreading.md","articles/tech/blazor/blazor-wasm-multithreading.md","articles/tech/blazor/blazor-wasm-multithreading",{"_path":11030,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":11031,"description":11032,"date":11033,"tags":11034,"rowTypeId":18,"sitemap":11037,"body":11038,"_type":2738,"_id":14416,"_source":2740,"_file":14417,"_stem":14418,"_extension":2743},"/articles/tech/blazor/indexeddb-blazor-interop","C#オブジェクトがIndexedDBに着くまで｜Blazor WASM永続化のデータ形式の旅","Blazor WebAssembly で C# のオブジェクトを IndexedDB に保存する際、データは MessagePack バイナリや Uint8Array と形を変えながら各レイヤーを通過します。PICOMでの実装を例に、境界ごとの責務分担と keyPath なしストア設計を解説します。","2026-04-20",[15,8778,11035,11036,13,17],"IndexedDB","JS Interop",{"loc":11030,"lastmod":11033,"priority":18},{"type":21,"children":11039,"toc":14399},[11040,11044,11063,11075,11114,11119,11124,11155,11160,11166,11199,11635,11674,11679,11684,11693,11698,11841,11866,11871,11882,11916,11929,12454,12482,12528,12534,12570,12583,12923,12935,13080,13085,13091,13116,13122,13140,13326,13344,13378,13453,13458,13464,13469,13756,13761,13769,13774,13786,13812,13936,13957,14337,14341,14391,14395],{"type":24,"tag":25,"props":11041,"children":11042},{"id":27},[11043],{"type":30,"value":27},{"type":24,"tag":32,"props":11045,"children":11046},{},[11047,11049,11054,11056,11061],{"type":30,"value":11048},"PICOMはオフラインで動く楽譜エディタなので、楽曲データはブラウザ内の",{"type":24,"tag":83,"props":11050,"children":11052},{"content":11051},"ブラウザに組み込まれたNoSQLデータベース。構造化データやバイナリを大量に保存できる",[11053],{"type":30,"value":11035},{"type":30,"value":11055},"に保存しています。C#の ",{"type":24,"tag":46,"props":11057,"children":11059},{"className":11058},[],[11060],{"type":30,"value":2109},{"type":30,"value":11062}," オブジェクトをそこまで落とすには、データは何段階かの形式変換を経る必要があります。",{"type":24,"tag":32,"props":11064,"children":11065},{},[11066,11068,11073],{"type":30,"value":11067},"この記事では、書き込み経路を追いながら、",{"type":24,"tag":38,"props":11069,"children":11070},{},[11071],{"type":30,"value":11072},"データがどういう姿で各レイヤーを通過し、C#/JS境界で誰が何に責任を持っているか",{"type":30,"value":11074},"を解説します。",{"type":24,"tag":92,"props":11076,"children":11077},{},[11078],{"type":24,"tag":32,"props":11079,"children":11080},{},[11081,11083,11088,11090,11096,11098,11104,11106,11112],{"type":30,"value":11082},"C#の ",{"type":24,"tag":46,"props":11084,"children":11086},{"className":11085},[],[11087],{"type":30,"value":2109},{"type":30,"value":11089}," は「MessagePackモデル → ",{"type":24,"tag":46,"props":11091,"children":11093},{"className":11092},[],[11094],{"type":30,"value":11095},"byte[]",{"type":30,"value":11097}," → (C#/JS境界) → ",{"type":24,"tag":46,"props":11099,"children":11101},{"className":11100},[],[11102],{"type":30,"value":11103},"Uint8Array",{"type":30,"value":11105}," → IndexedDB」のレイヤーを経て永続化されます。各境界の責務分担、",{"type":24,"tag":46,"props":11107,"children":11109},{"className":11108},[],[11110],{"type":30,"value":11111},"keyPath",{"type":30,"value":11113},"なしでストアを設計した理由などを解説します。",{"type":24,"tag":25,"props":11115,"children":11117},{"id":11116},"データの旅の全体像",[11118],{"type":30,"value":11116},{"type":24,"tag":32,"props":11120,"children":11121},{},[11122],{"type":30,"value":11123},"書き込み時、楽曲データが通過する形式の変化を示します。",{"type":24,"tag":4582,"props":11125,"children":11126},{},[11127,11132,11137,11145,11150],{"type":24,"tag":921,"props":11128,"children":11129},{},[11130],{"type":30,"value":11131},"C# Music (ドメイン)",{"type":24,"tag":921,"props":11133,"children":11134},{},[11135],{"type":30,"value":11136},"C# MusicDataModel (MessagePack用のオブジェクト)",{"type":24,"tag":921,"props":11138,"children":11139},{},[11140,11142],{"type":30,"value":11141},"C# byte",{"type":24,"tag":155,"props":11143,"children":11144},{},[],{"type":24,"tag":921,"props":11146,"children":11147},{},[11148],{"type":30,"value":11149},"JS Uint8Array",{"type":24,"tag":921,"props":11151,"children":11152},{},[11153],{"type":30,"value":11154},"IndexedDB musics ストア",{"type":24,"tag":32,"props":11156,"children":11157},{},[11158],{"type":30,"value":11159},"読み込みはこの逆順です。以降、各レイヤーで何が起きているかを順に追います。",{"type":24,"tag":25,"props":11161,"children":11163},{"id":11162},"レイヤー1-ドメインオブジェクト-messagepackモデル",[11164],{"type":30,"value":11165},"レイヤー1 ドメインオブジェクト → MessagePackモデル",{"type":24,"tag":32,"props":11167,"children":11168},{},[11169,11171,11176,11178,11183,11185,11190,11192,11197],{"type":30,"value":11170},"出発点の ",{"type":24,"tag":46,"props":11172,"children":11174},{"className":11173},[],[11175],{"type":30,"value":2109},{"type":30,"value":11177}," クラスは ",{"type":24,"tag":46,"props":11179,"children":11181},{"className":11180},[],[11182],{"type":30,"value":71},{"type":30,"value":11184}," を継承していて、プロパティ変更通知やイベント、他のドメインオブジェクトへの参照を持ちます。このまま直列化するとイベント購読の内部状態まで引きずってしまうので、",{"type":24,"tag":38,"props":11186,"children":11187},{},[11188],{"type":30,"value":11189},"シリアライズ専用のDTO",{"type":30,"value":11191},"に詰め直します。それが ",{"type":24,"tag":46,"props":11193,"children":11195},{"className":11194},[],[11196],{"type":30,"value":3012},{"type":30,"value":11198}," です。",{"type":24,"tag":145,"props":11200,"children":11202},{"className":147,"code":11201,"language":149,"meta":8,"style":8},"private static MusicDataModel ToDataModel(Music music)\n{\n    return new MusicDataModel\n    {\n        Version = 3,\n        Id = music.Id,\n        Metadata = new MusicMetadataModel\n        {\n            Title = music.Metadata.Title,\n            LastModified = DateTime.UtcNow,\n        },\n        Settings = new MusicSettingsModel\n        {\n            Tempo = music.Settings.Tempo,\n            Numerator = music.Settings.Numerator,\n            Denominator = music.Settings.Denominator,\n        },\n        Tracks = music.Tracks.Select(TrackToV2).ToArray(),\n    };\n}\n",[11203],{"type":24,"tag":46,"props":11204,"children":11205},{"__ignoreMap":8},[11206,11244,11251,11266,11273,11292,11321,11342,11349,11387,11415,11423,11444,11451,11487,11524,11561,11568,11621,11628],{"type":24,"tag":155,"props":11207,"children":11208},{"class":157,"line":18},[11209,11213,11218,11223,11228,11232,11236,11240],{"type":24,"tag":155,"props":11210,"children":11211},{"style":237},[11212],{"type":30,"value":1645},{"type":24,"tag":155,"props":11214,"children":11215},{"style":237},[11216],{"type":30,"value":11217}," static",{"type":24,"tag":155,"props":11219,"children":11220},{"style":167},[11221],{"type":30,"value":11222}," MusicDataModel",{"type":24,"tag":155,"props":11224,"children":11225},{"style":327},[11226],{"type":30,"value":11227}," ToDataModel",{"type":24,"tag":155,"props":11229,"children":11230},{"style":173},[11231],{"type":30,"value":551},{"type":24,"tag":155,"props":11233,"children":11234},{"style":167},[11235],{"type":30,"value":2109},{"type":24,"tag":155,"props":11237,"children":11238},{"style":327},[11239],{"type":30,"value":4184},{"type":24,"tag":155,"props":11241,"children":11242},{"style":173},[11243],{"type":30,"value":453},{"type":24,"tag":155,"props":11245,"children":11246},{"class":157,"line":189},[11247],{"type":24,"tag":155,"props":11248,"children":11249},{"style":173},[11250],{"type":30,"value":300},{"type":24,"tag":155,"props":11252,"children":11253},{"class":157,"line":223},[11254,11258,11262],{"type":24,"tag":155,"props":11255,"children":11256},{"style":161},[11257],{"type":30,"value":5045},{"type":24,"tag":155,"props":11259,"children":11260},{"style":237},[11261],{"type":30,"value":506},{"type":24,"tag":155,"props":11263,"children":11264},{"style":167},[11265],{"type":30,"value":3068},{"type":24,"tag":155,"props":11267,"children":11268},{"class":157,"line":233},[11269],{"type":24,"tag":155,"props":11270,"children":11271},{"style":173},[11272],{"type":30,"value":462},{"type":24,"tag":155,"props":11274,"children":11275},{"class":157,"line":252},[11276,11280,11284,11288],{"type":24,"tag":155,"props":11277,"children":11278},{"style":479},[11279],{"type":30,"value":4937},{"type":24,"tag":155,"props":11281,"children":11282},{"style":173},[11283],{"type":30,"value":443},{"type":24,"tag":155,"props":11285,"children":11286},{"style":3095},[11287],{"type":30,"value":5269},{"type":24,"tag":155,"props":11289,"children":11290},{"style":173},[11291],{"type":30,"value":4731},{"type":24,"tag":155,"props":11293,"children":11294},{"class":157,"line":260},[11295,11300,11304,11308,11312,11317],{"type":24,"tag":155,"props":11296,"children":11297},{"style":479},[11298],{"type":30,"value":11299},"        Id",{"type":24,"tag":155,"props":11301,"children":11302},{"style":173},[11303],{"type":30,"value":443},{"type":24,"tag":155,"props":11305,"children":11306},{"style":479},[11307],{"type":30,"value":4184},{"type":24,"tag":155,"props":11309,"children":11310},{"style":173},[11311],{"type":30,"value":176},{"type":24,"tag":155,"props":11313,"children":11314},{"style":479},[11315],{"type":30,"value":11316},"Id",{"type":24,"tag":155,"props":11318,"children":11319},{"style":173},[11320],{"type":30,"value":4731},{"type":24,"tag":155,"props":11322,"children":11323},{"class":157,"line":294},[11324,11329,11333,11337],{"type":24,"tag":155,"props":11325,"children":11326},{"style":479},[11327],{"type":30,"value":11328},"        Metadata",{"type":24,"tag":155,"props":11330,"children":11331},{"style":173},[11332],{"type":30,"value":443},{"type":24,"tag":155,"props":11334,"children":11335},{"style":237},[11336],{"type":30,"value":506},{"type":24,"tag":155,"props":11338,"children":11339},{"style":167},[11340],{"type":30,"value":11341}," MusicMetadataModel\n",{"type":24,"tag":155,"props":11343,"children":11344},{"class":157,"line":303},[11345],{"type":24,"tag":155,"props":11346,"children":11347},{"style":173},[11348],{"type":30,"value":7748},{"type":24,"tag":155,"props":11350,"children":11351},{"class":157,"line":337},[11352,11357,11361,11365,11369,11374,11378,11383],{"type":24,"tag":155,"props":11353,"children":11354},{"style":479},[11355],{"type":30,"value":11356},"            Title",{"type":24,"tag":155,"props":11358,"children":11359},{"style":173},[11360],{"type":30,"value":443},{"type":24,"tag":155,"props":11362,"children":11363},{"style":479},[11364],{"type":30,"value":4184},{"type":24,"tag":155,"props":11366,"children":11367},{"style":173},[11368],{"type":30,"value":176},{"type":24,"tag":155,"props":11370,"children":11371},{"style":479},[11372],{"type":30,"value":11373},"Metadata",{"type":24,"tag":155,"props":11375,"children":11376},{"style":173},[11377],{"type":30,"value":176},{"type":24,"tag":155,"props":11379,"children":11380},{"style":479},[11381],{"type":30,"value":11382},"Title",{"type":24,"tag":155,"props":11384,"children":11385},{"style":173},[11386],{"type":30,"value":4731},{"type":24,"tag":155,"props":11388,"children":11389},{"class":157,"line":345},[11390,11395,11399,11403,11407,11411],{"type":24,"tag":155,"props":11391,"children":11392},{"style":479},[11393],{"type":30,"value":11394},"            LastModified",{"type":24,"tag":155,"props":11396,"children":11397},{"style":173},[11398],{"type":30,"value":443},{"type":24,"tag":155,"props":11400,"children":11401},{"style":479},[11402],{"type":30,"value":4967},{"type":24,"tag":155,"props":11404,"children":11405},{"style":173},[11406],{"type":30,"value":176},{"type":24,"tag":155,"props":11408,"children":11409},{"style":479},[11410],{"type":30,"value":4976},{"type":24,"tag":155,"props":11412,"children":11413},{"style":173},[11414],{"type":30,"value":4731},{"type":24,"tag":155,"props":11416,"children":11417},{"class":157,"line":456},[11418],{"type":24,"tag":155,"props":11419,"children":11420},{"style":173},[11421],{"type":30,"value":11422},"        },\n",{"type":24,"tag":155,"props":11424,"children":11425},{"class":157,"line":465},[11426,11431,11435,11439],{"type":24,"tag":155,"props":11427,"children":11428},{"style":479},[11429],{"type":30,"value":11430},"        Settings",{"type":24,"tag":155,"props":11432,"children":11433},{"style":173},[11434],{"type":30,"value":443},{"type":24,"tag":155,"props":11436,"children":11437},{"style":237},[11438],{"type":30,"value":506},{"type":24,"tag":155,"props":11440,"children":11441},{"style":167},[11442],{"type":30,"value":11443}," MusicSettingsModel\n",{"type":24,"tag":155,"props":11445,"children":11446},{"class":157,"line":519},[11447],{"type":24,"tag":155,"props":11448,"children":11449},{"style":173},[11450],{"type":30,"value":7748},{"type":24,"tag":155,"props":11452,"children":11453},{"class":157,"line":540},[11454,11459,11463,11467,11471,11475,11479,11483],{"type":24,"tag":155,"props":11455,"children":11456},{"style":479},[11457],{"type":30,"value":11458},"            Tempo",{"type":24,"tag":155,"props":11460,"children":11461},{"style":173},[11462],{"type":30,"value":443},{"type":24,"tag":155,"props":11464,"children":11465},{"style":479},[11466],{"type":30,"value":4184},{"type":24,"tag":155,"props":11468,"children":11469},{"style":173},[11470],{"type":30,"value":176},{"type":24,"tag":155,"props":11472,"children":11473},{"style":479},[11474],{"type":30,"value":9795},{"type":24,"tag":155,"props":11476,"children":11477},{"style":173},[11478],{"type":30,"value":176},{"type":24,"tag":155,"props":11480,"children":11481},{"style":479},[11482],{"type":30,"value":9804},{"type":24,"tag":155,"props":11484,"children":11485},{"style":173},[11486],{"type":30,"value":4731},{"type":24,"tag":155,"props":11488,"children":11489},{"class":157,"line":563},[11490,11495,11499,11503,11507,11511,11515,11520],{"type":24,"tag":155,"props":11491,"children":11492},{"style":479},[11493],{"type":30,"value":11494},"            Numerator",{"type":24,"tag":155,"props":11496,"children":11497},{"style":173},[11498],{"type":30,"value":443},{"type":24,"tag":155,"props":11500,"children":11501},{"style":479},[11502],{"type":30,"value":4184},{"type":24,"tag":155,"props":11504,"children":11505},{"style":173},[11506],{"type":30,"value":176},{"type":24,"tag":155,"props":11508,"children":11509},{"style":479},[11510],{"type":30,"value":9795},{"type":24,"tag":155,"props":11512,"children":11513},{"style":173},[11514],{"type":30,"value":176},{"type":24,"tag":155,"props":11516,"children":11517},{"style":479},[11518],{"type":30,"value":11519},"Numerator",{"type":24,"tag":155,"props":11521,"children":11522},{"style":173},[11523],{"type":30,"value":4731},{"type":24,"tag":155,"props":11525,"children":11526},{"class":157,"line":572},[11527,11532,11536,11540,11544,11548,11552,11557],{"type":24,"tag":155,"props":11528,"children":11529},{"style":479},[11530],{"type":30,"value":11531},"            Denominator",{"type":24,"tag":155,"props":11533,"children":11534},{"style":173},[11535],{"type":30,"value":443},{"type":24,"tag":155,"props":11537,"children":11538},{"style":479},[11539],{"type":30,"value":4184},{"type":24,"tag":155,"props":11541,"children":11542},{"style":173},[11543],{"type":30,"value":176},{"type":24,"tag":155,"props":11545,"children":11546},{"style":479},[11547],{"type":30,"value":9795},{"type":24,"tag":155,"props":11549,"children":11550},{"style":173},[11551],{"type":30,"value":176},{"type":24,"tag":155,"props":11553,"children":11554},{"style":479},[11555],{"type":30,"value":11556},"Denominator",{"type":24,"tag":155,"props":11558,"children":11559},{"style":173},[11560],{"type":30,"value":4731},{"type":24,"tag":155,"props":11562,"children":11563},{"class":157,"line":580},[11564],{"type":24,"tag":155,"props":11565,"children":11566},{"style":173},[11567],{"type":30,"value":11422},{"type":24,"tag":155,"props":11569,"children":11570},{"class":157,"line":614},[11571,11576,11580,11584,11588,11592,11596,11600,11604,11609,11613,11617],{"type":24,"tag":155,"props":11572,"children":11573},{"style":479},[11574],{"type":30,"value":11575},"        Tracks",{"type":24,"tag":155,"props":11577,"children":11578},{"style":173},[11579],{"type":30,"value":443},{"type":24,"tag":155,"props":11581,"children":11582},{"style":479},[11583],{"type":30,"value":4184},{"type":24,"tag":155,"props":11585,"children":11586},{"style":173},[11587],{"type":30,"value":176},{"type":24,"tag":155,"props":11589,"children":11590},{"style":479},[11591],{"type":30,"value":4390},{"type":24,"tag":155,"props":11593,"children":11594},{"style":173},[11595],{"type":30,"value":176},{"type":24,"tag":155,"props":11597,"children":11598},{"style":327},[11599],{"type":30,"value":5005},{"type":24,"tag":155,"props":11601,"children":11602},{"style":173},[11603],{"type":30,"value":551},{"type":24,"tag":155,"props":11605,"children":11606},{"style":479},[11607],{"type":30,"value":11608},"TrackToV2",{"type":24,"tag":155,"props":11610,"children":11611},{"style":173},[11612],{"type":30,"value":5019},{"type":24,"tag":155,"props":11614,"children":11615},{"style":327},[11616],{"type":30,"value":5024},{"type":24,"tag":155,"props":11618,"children":11619},{"style":173},[11620],{"type":30,"value":5029},{"type":24,"tag":155,"props":11622,"children":11623},{"class":157,"line":622},[11624],{"type":24,"tag":155,"props":11625,"children":11626},{"style":173},[11627],{"type":30,"value":5037},{"type":24,"tag":155,"props":11629,"children":11630},{"class":157,"line":680},[11631],{"type":24,"tag":155,"props":11632,"children":11633},{"style":173},[11634],{"type":30,"value":694},{"type":24,"tag":32,"props":11636,"children":11637},{},[11638,11643,11645,11650,11652,11657,11659,11664,11666,11672],{"type":24,"tag":46,"props":11639,"children":11641},{"className":11640},[],[11642],{"type":30,"value":3012},{"type":30,"value":11644}," は ",{"type":24,"tag":46,"props":11646,"children":11648},{"className":11647},[],[11649],{"type":30,"value":3019},{"type":30,"value":11651}," 属性を持ち、各フィールドが ",{"type":24,"tag":46,"props":11653,"children":11655},{"className":11654},[],[11656],{"type":30,"value":2920},{"type":30,"value":11658}," で番号付けされます。ドメイン側の ",{"type":24,"tag":46,"props":11660,"children":11662},{"className":11661},[],[11663],{"type":30,"value":2109},{"type":30,"value":11665}," は永続化の都合を一切知らず、",{"type":24,"tag":46,"props":11667,"children":11669},{"className":11668},[],[11670],{"type":30,"value":11671},"PIMRepository",{"type":30,"value":11673}," という永続化層がこの変換の責任だけを負います。",{"type":24,"tag":875,"props":11675,"children":11677},{"id":11676},"なぜドメインモデルを直接永続化しないか",[11678],{"type":30,"value":11676},{"type":24,"tag":32,"props":11680,"children":11681},{},[11682],{"type":30,"value":11683},"ドメインモデルの変化のサイクルと保存形式のデータ構造変化のサイクルは異なるからです。\n例えば、保存に適したデータ構造へ変更したい場合を考えます。分離していないとドメインモデルを直接改良する必要がありますが、分離していれば DTO と詰め替え処理の改善だけで済みます。\nこうすることで、インフラレイヤの変更がドメインレイヤへ波及することを防ぐことができます。",{"type":24,"tag":25,"props":11685,"children":11687},{"id":11686},"レイヤー2-messagepackモデル-byte",[11688,11690],{"type":30,"value":11689},"レイヤー2 MessagePackモデル → byte",{"type":24,"tag":155,"props":11691,"children":11692},{},[],{"type":24,"tag":32,"props":11694,"children":11695},{},[11696],{"type":30,"value":11697},"ここは一行で終わります。",{"type":24,"tag":145,"props":11699,"children":11701},{"className":147,"code":11700,"language":149,"meta":8,"style":8},"public byte[] Write(Music music)\n{\n    var model = ToDataModel(music);\n    var bin = MessagePackSerializer.Serialize(model);\n    return bin;\n}\n",[11702],{"type":24,"tag":46,"props":11703,"children":11704},{"__ignoreMap":8},[11705,11741,11748,11779,11819,11834],{"type":24,"tag":155,"props":11706,"children":11707},{"class":157,"line":18},[11708,11712,11716,11720,11725,11729,11733,11737],{"type":24,"tag":155,"props":11709,"children":11710},{"style":237},[11711],{"type":30,"value":266},{"type":24,"tag":155,"props":11713,"children":11714},{"style":161},[11715],{"type":30,"value":4850},{"type":24,"tag":155,"props":11717,"children":11718},{"style":173},[11719],{"type":30,"value":3373},{"type":24,"tag":155,"props":11721,"children":11722},{"style":327},[11723],{"type":30,"value":11724}," Write",{"type":24,"tag":155,"props":11726,"children":11727},{"style":173},[11728],{"type":30,"value":551},{"type":24,"tag":155,"props":11730,"children":11731},{"style":167},[11732],{"type":30,"value":2109},{"type":24,"tag":155,"props":11734,"children":11735},{"style":327},[11736],{"type":30,"value":4184},{"type":24,"tag":155,"props":11738,"children":11739},{"style":173},[11740],{"type":30,"value":453},{"type":24,"tag":155,"props":11742,"children":11743},{"class":157,"line":189},[11744],{"type":24,"tag":155,"props":11745,"children":11746},{"style":173},[11747],{"type":30,"value":300},{"type":24,"tag":155,"props":11749,"children":11750},{"class":157,"line":223},[11751,11755,11759,11763,11767,11771,11775],{"type":24,"tag":155,"props":11752,"children":11753},{"style":237},[11754],{"type":30,"value":4904},{"type":24,"tag":155,"props":11756,"children":11757},{"style":327},[11758],{"type":30,"value":4248},{"type":24,"tag":155,"props":11760,"children":11761},{"style":173},[11762],{"type":30,"value":443},{"type":24,"tag":155,"props":11764,"children":11765},{"style":327},[11766],{"type":30,"value":11227},{"type":24,"tag":155,"props":11768,"children":11769},{"style":173},[11770],{"type":30,"value":551},{"type":24,"tag":155,"props":11772,"children":11773},{"style":479},[11774],{"type":30,"value":2166},{"type":24,"tag":155,"props":11776,"children":11777},{"style":173},[11778],{"type":30,"value":560},{"type":24,"tag":155,"props":11780,"children":11781},{"class":157,"line":233},[11782,11786,11791,11795,11799,11803,11807,11811,11815],{"type":24,"tag":155,"props":11783,"children":11784},{"style":237},[11785],{"type":30,"value":4904},{"type":24,"tag":155,"props":11787,"children":11788},{"style":327},[11789],{"type":30,"value":11790}," bin",{"type":24,"tag":155,"props":11792,"children":11793},{"style":173},[11794],{"type":30,"value":443},{"type":24,"tag":155,"props":11796,"children":11797},{"style":479},[11798],{"type":30,"value":4284},{"type":24,"tag":155,"props":11800,"children":11801},{"style":173},[11802],{"type":30,"value":176},{"type":24,"tag":155,"props":11804,"children":11805},{"style":327},[11806],{"type":30,"value":5058},{"type":24,"tag":155,"props":11808,"children":11809},{"style":173},[11810],{"type":30,"value":551},{"type":24,"tag":155,"props":11812,"children":11813},{"style":479},[11814],{"type":30,"value":4355},{"type":24,"tag":155,"props":11816,"children":11817},{"style":173},[11818],{"type":30,"value":560},{"type":24,"tag":155,"props":11820,"children":11821},{"class":157,"line":252},[11822,11826,11830],{"type":24,"tag":155,"props":11823,"children":11824},{"style":161},[11825],{"type":30,"value":5045},{"type":24,"tag":155,"props":11827,"children":11828},{"style":479},[11829],{"type":30,"value":11790},{"type":24,"tag":155,"props":11831,"children":11832},{"style":173},[11833],{"type":30,"value":186},{"type":24,"tag":155,"props":11835,"children":11836},{"class":157,"line":260},[11837],{"type":24,"tag":155,"props":11838,"children":11839},{"style":173},[11840],{"type":30,"value":694},{"type":24,"tag":32,"props":11842,"children":11843},{},[11844,11850,11852,11857,11859,11864],{"type":24,"tag":46,"props":11845,"children":11847},{"className":11846},[],[11848],{"type":30,"value":11849},"MessagePackSerializer.Serialize",{"type":30,"value":11851}," が ",{"type":24,"tag":46,"props":11853,"children":11855},{"className":11854},[],[11856],{"type":30,"value":3012},{"type":30,"value":11858}," を純粋な ",{"type":24,"tag":46,"props":11860,"children":11862},{"className":11861},[],[11863],{"type":30,"value":11095},{"type":30,"value":11865}," に落とします。ここまでは完全にC#内で完結する話で、interopの世界にはまだ触れていません。なぜJSONではなくMessagePackを選んだのかや、バージョニング戦略は、別記事で詳しく書いているのでそちらを参照してください。",{"type":24,"tag":11867,"props":11868,"children":11870},"link-card",{"label":11869,"to":2745},"MessagePackで独自バイナリフォーマットをバージョン管理する話はこちら⬇️",[],{"type":24,"tag":25,"props":11872,"children":11874},{"id":11873},"レイヤー3-cjs境界を越える-byteを直接渡す",[11875,11877,11880],{"type":30,"value":11876},"レイヤー3 C#/JS境界を越える — byte",{"type":24,"tag":155,"props":11878,"children":11879},{},[],{"type":30,"value":11881},"を直接渡す",{"type":24,"tag":32,"props":11883,"children":11884},{},[11885,11887,11893,11895,11900,11902,11914],{"type":30,"value":11886},"ここが記事の中心です。PICOMは.NET 10のBlazor WebAssemblyで動いているので、",{"type":24,"tag":46,"props":11888,"children":11890},{"className":11889},[],[11891],{"type":30,"value":11892},"IJSRuntime.InvokeVoidAsync",{"type":30,"value":11894}," の引数に ",{"type":24,"tag":46,"props":11896,"children":11898},{"className":11897},[],[11899],{"type":30,"value":11095},{"type":30,"value":11901}," を渡すと、",{"type":24,"tag":38,"props":11903,"children":11904},{},[11905,11907,11912],{"type":30,"value":11906},"JSON経由ではなく ",{"type":24,"tag":46,"props":11908,"children":11910},{"className":11909},[],[11911],{"type":30,"value":11103},{"type":30,"value":11913}," として直接",{"type":30,"value":11915},"渡されます。これは.NET 6からの機能で、Base64で文字列化する必要はありません。",{"type":24,"tag":32,"props":11917,"children":11918},{},[11919,11921,11927],{"type":30,"value":11920},"境界を越えるコードは ",{"type":24,"tag":46,"props":11922,"children":11924},{"className":11923},[],[11925],{"type":30,"value":11926},"MusicsDataBase",{"type":30,"value":11928}," クラスにあります。",{"type":24,"tag":145,"props":11930,"children":11932},{"className":147,"code":11931,"language":149,"meta":8,"style":8},"public class MusicsDataBase(IJSRuntime js)\n{\n    private readonly IJSRuntime _js = js;\n    private IJSObjectReference? _db;\n\n    private async Task\u003CIJSObjectReference> EnsureDbAsync()\n    {\n        if (_db is null)\n        {\n            var dbModule = await _js.InvokeAsync\u003CIJSObjectReference>(\"import\", \"/db.js\");\n            _db = await dbModule.InvokeAsync\u003CIJSObjectReference>(\"useDatabase\");\n        }\n        return _db;\n    }\n\n    public async Task SetAsync(int musicId, byte[] pimData)\n    {\n        var db = await EnsureDbAsync();\n        await db.InvokeVoidAsync(\"setItem\", musicId, pimData);\n    }\n}\n",[11933],{"type":24,"tag":46,"props":11934,"children":11935},{"__ignoreMap":8},[11936,11970,11977,12010,12035,12042,12079,12086,12114,12121,12202,12259,12266,12281,12288,12295,12348,12355,12383,12440,12447],{"type":24,"tag":155,"props":11937,"children":11938},{"class":157,"line":18},[11939,11943,11947,11952,11956,11961,11966],{"type":24,"tag":155,"props":11940,"children":11941},{"style":237},[11942],{"type":30,"value":266},{"type":24,"tag":155,"props":11944,"children":11945},{"style":237},[11946],{"type":30,"value":276},{"type":24,"tag":155,"props":11948,"children":11949},{"style":167},[11950],{"type":30,"value":11951}," MusicsDataBase",{"type":24,"tag":155,"props":11953,"children":11954},{"style":173},[11955],{"type":30,"value":551},{"type":24,"tag":155,"props":11957,"children":11958},{"style":167},[11959],{"type":30,"value":11960},"IJSRuntime",{"type":24,"tag":155,"props":11962,"children":11963},{"style":327},[11964],{"type":30,"value":11965}," js",{"type":24,"tag":155,"props":11967,"children":11968},{"style":173},[11969],{"type":30,"value":453},{"type":24,"tag":155,"props":11971,"children":11972},{"class":157,"line":189},[11973],{"type":24,"tag":155,"props":11974,"children":11975},{"style":173},[11976],{"type":30,"value":300},{"type":24,"tag":155,"props":11978,"children":11979},{"class":157,"line":223},[11980,11984,11988,11993,11998,12002,12006],{"type":24,"tag":155,"props":11981,"children":11982},{"style":237},[11983],{"type":30,"value":752},{"type":24,"tag":155,"props":11985,"children":11986},{"style":237},[11987],{"type":30,"value":6093},{"type":24,"tag":155,"props":11989,"children":11990},{"style":167},[11991],{"type":30,"value":11992}," IJSRuntime",{"type":24,"tag":155,"props":11994,"children":11995},{"style":327},[11996],{"type":30,"value":11997}," _js",{"type":24,"tag":155,"props":11999,"children":12000},{"style":173},[12001],{"type":30,"value":443},{"type":24,"tag":155,"props":12003,"children":12004},{"style":479},[12005],{"type":30,"value":11965},{"type":24,"tag":155,"props":12007,"children":12008},{"style":173},[12009],{"type":30,"value":186},{"type":24,"tag":155,"props":12011,"children":12012},{"class":157,"line":233},[12013,12017,12022,12026,12031],{"type":24,"tag":155,"props":12014,"children":12015},{"style":237},[12016],{"type":30,"value":752},{"type":24,"tag":155,"props":12018,"children":12019},{"style":167},[12020],{"type":30,"value":12021}," IJSObjectReference",{"type":24,"tag":155,"props":12023,"children":12024},{"style":173},[12025],{"type":30,"value":324},{"type":24,"tag":155,"props":12027,"children":12028},{"style":327},[12029],{"type":30,"value":12030}," _db",{"type":24,"tag":155,"props":12032,"children":12033},{"style":173},[12034],{"type":30,"value":186},{"type":24,"tag":155,"props":12036,"children":12037},{"class":157,"line":252},[12038],{"type":24,"tag":155,"props":12039,"children":12040},{"emptyLinePlaceholder":227},[12041],{"type":30,"value":230},{"type":24,"tag":155,"props":12043,"children":12044},{"class":157,"line":260},[12045,12049,12053,12057,12061,12066,12070,12075],{"type":24,"tag":155,"props":12046,"children":12047},{"style":237},[12048],{"type":30,"value":752},{"type":24,"tag":155,"props":12050,"children":12051},{"style":237},[12052],{"type":30,"value":10705},{"type":24,"tag":155,"props":12054,"children":12055},{"style":167},[12056],{"type":30,"value":9682},{"type":24,"tag":155,"props":12058,"children":12059},{"style":173},[12060],{"type":30,"value":366},{"type":24,"tag":155,"props":12062,"children":12063},{"style":167},[12064],{"type":30,"value":12065},"IJSObjectReference",{"type":24,"tag":155,"props":12067,"children":12068},{"style":173},[12069],{"type":30,"value":1028},{"type":24,"tag":155,"props":12071,"children":12072},{"style":327},[12073],{"type":30,"value":12074}," EnsureDbAsync",{"type":24,"tag":155,"props":12076,"children":12077},{"style":173},[12078],{"type":30,"value":7082},{"type":24,"tag":155,"props":12080,"children":12081},{"class":157,"line":294},[12082],{"type":24,"tag":155,"props":12083,"children":12084},{"style":173},[12085],{"type":30,"value":462},{"type":24,"tag":155,"props":12087,"children":12088},{"class":157,"line":303},[12089,12093,12097,12102,12106,12110],{"type":24,"tag":155,"props":12090,"children":12091},{"style":161},[12092],{"type":30,"value":471},{"type":24,"tag":155,"props":12094,"children":12095},{"style":173},[12096],{"type":30,"value":476},{"type":24,"tag":155,"props":12098,"children":12099},{"style":479},[12100],{"type":30,"value":12101},"_db",{"type":24,"tag":155,"props":12103,"children":12104},{"style":237},[12105],{"type":30,"value":487},{"type":24,"tag":155,"props":12107,"children":12108},{"style":237},[12109],{"type":30,"value":448},{"type":24,"tag":155,"props":12111,"children":12112},{"style":173},[12113],{"type":30,"value":453},{"type":24,"tag":155,"props":12115,"children":12116},{"class":157,"line":337},[12117],{"type":24,"tag":155,"props":12118,"children":12119},{"style":173},[12120],{"type":30,"value":7748},{"type":24,"tag":155,"props":12122,"children":12123},{"class":157,"line":345},[12124,12129,12134,12138,12143,12147,12151,12156,12160,12164,12168,12172,12177,12181,12185,12189,12194,12198],{"type":24,"tag":155,"props":12125,"children":12126},{"style":237},[12127],{"type":30,"value":12128},"            var",{"type":24,"tag":155,"props":12130,"children":12131},{"style":327},[12132],{"type":30,"value":12133}," dbModule",{"type":24,"tag":155,"props":12135,"children":12136},{"style":173},[12137],{"type":30,"value":443},{"type":24,"tag":155,"props":12139,"children":12140},{"style":237},[12141],{"type":30,"value":12142}," await",{"type":24,"tag":155,"props":12144,"children":12145},{"style":479},[12146],{"type":30,"value":11997},{"type":24,"tag":155,"props":12148,"children":12149},{"style":173},[12150],{"type":30,"value":176},{"type":24,"tag":155,"props":12152,"children":12153},{"style":327},[12154],{"type":30,"value":12155},"InvokeAsync",{"type":24,"tag":155,"props":12157,"children":12158},{"style":173},[12159],{"type":30,"value":366},{"type":24,"tag":155,"props":12161,"children":12162},{"style":167},[12163],{"type":30,"value":12065},{"type":24,"tag":155,"props":12165,"children":12166},{"style":173},[12167],{"type":30,"value":376},{"type":24,"tag":155,"props":12169,"children":12170},{"style":768},[12171],{"type":30,"value":2246},{"type":24,"tag":155,"props":12173,"children":12174},{"style":2239},[12175],{"type":30,"value":12176},"import",{"type":24,"tag":155,"props":12178,"children":12179},{"style":768},[12180],{"type":30,"value":2246},{"type":24,"tag":155,"props":12182,"children":12183},{"style":173},[12184],{"type":30,"value":396},{"type":24,"tag":155,"props":12186,"children":12187},{"style":768},[12188],{"type":30,"value":2236},{"type":24,"tag":155,"props":12190,"children":12191},{"style":2239},[12192],{"type":30,"value":12193},"/db.js",{"type":24,"tag":155,"props":12195,"children":12196},{"style":768},[12197],{"type":30,"value":2246},{"type":24,"tag":155,"props":12199,"children":12200},{"style":173},[12201],{"type":30,"value":560},{"type":24,"tag":155,"props":12203,"children":12204},{"class":157,"line":456},[12205,12210,12214,12218,12222,12226,12230,12234,12238,12242,12246,12251,12255],{"type":24,"tag":155,"props":12206,"children":12207},{"style":479},[12208],{"type":30,"value":12209},"            _db",{"type":24,"tag":155,"props":12211,"children":12212},{"style":173},[12213],{"type":30,"value":443},{"type":24,"tag":155,"props":12215,"children":12216},{"style":237},[12217],{"type":30,"value":12142},{"type":24,"tag":155,"props":12219,"children":12220},{"style":479},[12221],{"type":30,"value":12133},{"type":24,"tag":155,"props":12223,"children":12224},{"style":173},[12225],{"type":30,"value":176},{"type":24,"tag":155,"props":12227,"children":12228},{"style":327},[12229],{"type":30,"value":12155},{"type":24,"tag":155,"props":12231,"children":12232},{"style":173},[12233],{"type":30,"value":366},{"type":24,"tag":155,"props":12235,"children":12236},{"style":167},[12237],{"type":30,"value":12065},{"type":24,"tag":155,"props":12239,"children":12240},{"style":173},[12241],{"type":30,"value":376},{"type":24,"tag":155,"props":12243,"children":12244},{"style":768},[12245],{"type":30,"value":2246},{"type":24,"tag":155,"props":12247,"children":12248},{"style":2239},[12249],{"type":30,"value":12250},"useDatabase",{"type":24,"tag":155,"props":12252,"children":12253},{"style":768},[12254],{"type":30,"value":2246},{"type":24,"tag":155,"props":12256,"children":12257},{"style":173},[12258],{"type":30,"value":560},{"type":24,"tag":155,"props":12260,"children":12261},{"class":157,"line":465},[12262],{"type":24,"tag":155,"props":12263,"children":12264},{"style":173},[12265],{"type":30,"value":7785},{"type":24,"tag":155,"props":12267,"children":12268},{"class":157,"line":519},[12269,12273,12277],{"type":24,"tag":155,"props":12270,"children":12271},{"style":161},[12272],{"type":30,"value":4517},{"type":24,"tag":155,"props":12274,"children":12275},{"style":479},[12276],{"type":30,"value":12030},{"type":24,"tag":155,"props":12278,"children":12279},{"style":173},[12280],{"type":30,"value":186},{"type":24,"tag":155,"props":12282,"children":12283},{"class":157,"line":540},[12284],{"type":24,"tag":155,"props":12285,"children":12286},{"style":173},[12287],{"type":30,"value":569},{"type":24,"tag":155,"props":12289,"children":12290},{"class":157,"line":563},[12291],{"type":24,"tag":155,"props":12292,"children":12293},{"emptyLinePlaceholder":227},[12294],{"type":30,"value":230},{"type":24,"tag":155,"props":12296,"children":12297},{"class":157,"line":572},[12298,12302,12306,12310,12315,12319,12323,12327,12331,12335,12339,12344],{"type":24,"tag":155,"props":12299,"children":12300},{"style":237},[12301],{"type":30,"value":309},{"type":24,"tag":155,"props":12303,"children":12304},{"style":237},[12305],{"type":30,"value":10705},{"type":24,"tag":155,"props":12307,"children":12308},{"style":167},[12309],{"type":30,"value":9682},{"type":24,"tag":155,"props":12311,"children":12312},{"style":327},[12313],{"type":30,"value":12314}," SetAsync",{"type":24,"tag":155,"props":12316,"children":12317},{"style":173},[12318],{"type":30,"value":551},{"type":24,"tag":155,"props":12320,"children":12321},{"style":161},[12322],{"type":30,"value":6740},{"type":24,"tag":155,"props":12324,"children":12325},{"style":327},[12326],{"type":30,"value":4224},{"type":24,"tag":155,"props":12328,"children":12329},{"style":173},[12330],{"type":30,"value":396},{"type":24,"tag":155,"props":12332,"children":12333},{"style":161},[12334],{"type":30,"value":4850},{"type":24,"tag":155,"props":12336,"children":12337},{"style":173},[12338],{"type":30,"value":3373},{"type":24,"tag":155,"props":12340,"children":12341},{"style":327},[12342],{"type":30,"value":12343}," pimData",{"type":24,"tag":155,"props":12345,"children":12346},{"style":173},[12347],{"type":30,"value":453},{"type":24,"tag":155,"props":12349,"children":12350},{"class":157,"line":580},[12351],{"type":24,"tag":155,"props":12352,"children":12353},{"style":173},[12354],{"type":30,"value":462},{"type":24,"tag":155,"props":12356,"children":12357},{"class":157,"line":614},[12358,12362,12367,12371,12375,12379],{"type":24,"tag":155,"props":12359,"children":12360},{"style":237},[12361],{"type":30,"value":7133},{"type":24,"tag":155,"props":12363,"children":12364},{"style":327},[12365],{"type":30,"value":12366}," db",{"type":24,"tag":155,"props":12368,"children":12369},{"style":173},[12370],{"type":30,"value":443},{"type":24,"tag":155,"props":12372,"children":12373},{"style":237},[12374],{"type":30,"value":12142},{"type":24,"tag":155,"props":12376,"children":12377},{"style":327},[12378],{"type":30,"value":12074},{"type":24,"tag":155,"props":12380,"children":12381},{"style":173},[12382],{"type":30,"value":516},{"type":24,"tag":155,"props":12384,"children":12385},{"class":157,"line":622},[12386,12390,12394,12398,12403,12407,12411,12416,12420,12424,12428,12432,12436],{"type":24,"tag":155,"props":12387,"children":12388},{"style":237},[12389],{"type":30,"value":10896},{"type":24,"tag":155,"props":12391,"children":12392},{"style":479},[12393],{"type":30,"value":12366},{"type":24,"tag":155,"props":12395,"children":12396},{"style":173},[12397],{"type":30,"value":176},{"type":24,"tag":155,"props":12399,"children":12400},{"style":327},[12401],{"type":30,"value":12402},"InvokeVoidAsync",{"type":24,"tag":155,"props":12404,"children":12405},{"style":173},[12406],{"type":30,"value":551},{"type":24,"tag":155,"props":12408,"children":12409},{"style":768},[12410],{"type":30,"value":2246},{"type":24,"tag":155,"props":12412,"children":12413},{"style":2239},[12414],{"type":30,"value":12415},"setItem",{"type":24,"tag":155,"props":12417,"children":12418},{"style":768},[12419],{"type":30,"value":2246},{"type":24,"tag":155,"props":12421,"children":12422},{"style":173},[12423],{"type":30,"value":396},{"type":24,"tag":155,"props":12425,"children":12426},{"style":479},[12427],{"type":30,"value":4224},{"type":24,"tag":155,"props":12429,"children":12430},{"style":173},[12431],{"type":30,"value":396},{"type":24,"tag":155,"props":12433,"children":12434},{"style":479},[12435],{"type":30,"value":12343},{"type":24,"tag":155,"props":12437,"children":12438},{"style":173},[12439],{"type":30,"value":560},{"type":24,"tag":155,"props":12441,"children":12442},{"class":157,"line":680},[12443],{"type":24,"tag":155,"props":12444,"children":12445},{"style":173},[12446],{"type":30,"value":569},{"type":24,"tag":155,"props":12448,"children":12449},{"class":157,"line":688},[12450],{"type":24,"tag":155,"props":12451,"children":12452},{"style":173},[12453],{"type":30,"value":694},{"type":24,"tag":32,"props":12455,"children":12456},{},[12457,12459,12464,12466,12472,12474,12480],{"type":30,"value":12458},"3つの ",{"type":24,"tag":46,"props":12460,"children":12462},{"className":12461},[],[12463],{"type":30,"value":12065},{"type":30,"value":12465}," 階層に分かれています。",{"type":24,"tag":46,"props":12467,"children":12469},{"className":12468},[],[12470],{"type":30,"value":12471},"import('/db.js')",{"type":30,"value":12473}," で取得したモジュールと、そのモジュールの ",{"type":24,"tag":46,"props":12475,"children":12477},{"className":12476},[],[12478],{"type":30,"value":12479},"useDatabase()",{"type":30,"value":12481}," が返すDBハンドル、それから各メソッド呼び出し。という流れになります。",{"type":24,"tag":32,"props":12483,"children":12484},{},[12485,12491,12493,12499,12500,12505,12507,12512,12514,12519,12521,12526],{"type":24,"tag":46,"props":12486,"children":12488},{"className":12487},[],[12489],{"type":30,"value":12490},"InvokeVoidAsync(\"setItem\", musicId, pimData)",{"type":30,"value":12492}," の ",{"type":24,"tag":46,"props":12494,"children":12496},{"className":12495},[],[12497],{"type":30,"value":12498},"pimData",{"type":30,"value":11644},{"type":24,"tag":46,"props":12501,"children":12503},{"className":12502},[],[12504],{"type":30,"value":11095},{"type":30,"value":12506},"。これが ",{"type":24,"tag":46,"props":12508,"children":12510},{"className":12509},[],[12511],{"type":30,"value":11960},{"type":30,"value":12513}," の層で ",{"type":24,"tag":46,"props":12515,"children":12517},{"className":12516},[],[12518],{"type":30,"value":11103},{"type":30,"value":12520}," に変換され、JS側の ",{"type":24,"tag":46,"props":12522,"children":12524},{"className":12523},[],[12525],{"type":30,"value":12415},{"type":30,"value":12527}," 関数の引数として届きます。",{"type":24,"tag":25,"props":12529,"children":12531},{"id":12530},"レイヤー4-js側はuint8arrayを受け取ってそのまま使える",[12532],{"type":30,"value":12533},"レイヤー4 JS側はUint8Arrayを受け取ってそのまま使える",{"type":24,"tag":32,"props":12535,"children":12536},{},[12537,12539,12545,12547,12552,12554,12560,12562,12568],{"type":30,"value":12538},"JS側の ",{"type":24,"tag":46,"props":12540,"children":12542},{"className":12541},[],[12543],{"type":30,"value":12544},"db.js",{"type":30,"value":12546}," では、C#から渡ってきた ",{"type":24,"tag":46,"props":12548,"children":12550},{"className":12549},[],[12551],{"type":30,"value":11103},{"type":30,"value":12553}," をそのまま IndexedDB に渡せます。",{"type":24,"tag":46,"props":12555,"children":12557},{"className":12556},[],[12558],{"type":30,"value":12559},"atob",{"type":30,"value":12561}," や ",{"type":24,"tag":46,"props":12563,"children":12565},{"className":12564},[],[12566],{"type":30,"value":12567},"Uint8Array.from",{"type":30,"value":12569}," による変換コードは一切ありません。",{"type":24,"tag":32,"props":12571,"children":12572},{},[12573,12575,12581],{"type":30,"value":12574},"コードを見る前に、あとで繰り返し登場する ",{"type":24,"tag":46,"props":12576,"children":12578},{"className":12577},[],[12579],{"type":30,"value":12580},"runTransaction",{"type":30,"value":12582}," というヘルパーを先に定義しておきます。IndexedDB のトランザクションは「open → objectStore 取得 → リクエスト発行 → onsuccess/onerror を受ける」というボイラープレートが毎回必要なので、一度 Promise にラップしてあります。",{"type":24,"tag":145,"props":12584,"children":12588},{"className":12585,"code":12586,"language":12587,"meta":8,"style":8},"language-javascript shiki shiki-themes vitesse-dark","const runTransaction = (mode, fn) =>\n    new Promise((resolve, reject) => {\n        const tx = db.transaction(MUSIC_STORE_NAME, mode);\n        const store = tx.objectStore(MUSIC_STORE_NAME);\n        const request = fn(store);\n        request.onsuccess = () => resolve(request.result);\n        request.onerror = () => reject(request.error);\n    });\n","javascript",[12589],{"type":24,"tag":46,"props":12590,"children":12591},{"__ignoreMap":8},[12592,12635,12681,12733,12774,12807,12863,12916],{"type":24,"tag":155,"props":12593,"children":12594},{"class":157,"line":18},[12595,12600,12605,12609,12613,12618,12622,12627,12631],{"type":24,"tag":155,"props":12596,"children":12597},{"style":237},[12598],{"type":30,"value":12599},"const",{"type":24,"tag":155,"props":12601,"children":12602},{"style":327},[12603],{"type":30,"value":12604}," runTransaction",{"type":24,"tag":155,"props":12606,"children":12607},{"style":173},[12608],{"type":30,"value":443},{"type":24,"tag":155,"props":12610,"children":12611},{"style":173},[12612],{"type":30,"value":476},{"type":24,"tag":155,"props":12614,"children":12615},{"style":479},[12616],{"type":30,"value":12617},"mode",{"type":24,"tag":155,"props":12619,"children":12620},{"style":173},[12621],{"type":30,"value":396},{"type":24,"tag":155,"props":12623,"children":12624},{"style":479},[12625],{"type":30,"value":12626}," fn",{"type":24,"tag":155,"props":12628,"children":12629},{"style":173},[12630],{"type":30,"value":496},{"type":24,"tag":155,"props":12632,"children":12633},{"style":173},[12634],{"type":30,"value":2183},{"type":24,"tag":155,"props":12636,"children":12637},{"class":157,"line":189},[12638,12643,12649,12654,12659,12663,12668,12672,12676],{"type":24,"tag":155,"props":12639,"children":12640},{"style":237},[12641],{"type":30,"value":12642},"    new",{"type":24,"tag":155,"props":12644,"children":12646},{"style":12645},"--shiki-default:#B8A965",[12647],{"type":30,"value":12648}," Promise",{"type":24,"tag":155,"props":12650,"children":12651},{"style":173},[12652],{"type":30,"value":12653},"((",{"type":24,"tag":155,"props":12655,"children":12656},{"style":479},[12657],{"type":30,"value":12658},"resolve",{"type":24,"tag":155,"props":12660,"children":12661},{"style":173},[12662],{"type":30,"value":396},{"type":24,"tag":155,"props":12664,"children":12665},{"style":479},[12666],{"type":30,"value":12667}," reject",{"type":24,"tag":155,"props":12669,"children":12670},{"style":173},[12671],{"type":30,"value":496},{"type":24,"tag":155,"props":12673,"children":12674},{"style":173},[12675],{"type":30,"value":811},{"type":24,"tag":155,"props":12677,"children":12678},{"style":173},[12679],{"type":30,"value":12680}," {\n",{"type":24,"tag":155,"props":12682,"children":12683},{"class":157,"line":223},[12684,12689,12694,12698,12702,12706,12711,12715,12720,12724,12729],{"type":24,"tag":155,"props":12685,"children":12686},{"style":237},[12687],{"type":30,"value":12688},"        const",{"type":24,"tag":155,"props":12690,"children":12691},{"style":479},[12692],{"type":30,"value":12693}," tx",{"type":24,"tag":155,"props":12695,"children":12696},{"style":173},[12697],{"type":30,"value":443},{"type":24,"tag":155,"props":12699,"children":12700},{"style":479},[12701],{"type":30,"value":12366},{"type":24,"tag":155,"props":12703,"children":12704},{"style":173},[12705],{"type":30,"value":176},{"type":24,"tag":155,"props":12707,"children":12708},{"style":327},[12709],{"type":30,"value":12710},"transaction",{"type":24,"tag":155,"props":12712,"children":12713},{"style":173},[12714],{"type":30,"value":551},{"type":24,"tag":155,"props":12716,"children":12717},{"style":479},[12718],{"type":30,"value":12719},"MUSIC_STORE_NAME",{"type":24,"tag":155,"props":12721,"children":12722},{"style":173},[12723],{"type":30,"value":396},{"type":24,"tag":155,"props":12725,"children":12726},{"style":479},[12727],{"type":30,"value":12728}," mode",{"type":24,"tag":155,"props":12730,"children":12731},{"style":173},[12732],{"type":30,"value":560},{"type":24,"tag":155,"props":12734,"children":12735},{"class":157,"line":233},[12736,12740,12745,12749,12753,12757,12762,12766,12770],{"type":24,"tag":155,"props":12737,"children":12738},{"style":237},[12739],{"type":30,"value":12688},{"type":24,"tag":155,"props":12741,"children":12742},{"style":479},[12743],{"type":30,"value":12744}," store",{"type":24,"tag":155,"props":12746,"children":12747},{"style":173},[12748],{"type":30,"value":443},{"type":24,"tag":155,"props":12750,"children":12751},{"style":479},[12752],{"type":30,"value":12693},{"type":24,"tag":155,"props":12754,"children":12755},{"style":173},[12756],{"type":30,"value":176},{"type":24,"tag":155,"props":12758,"children":12759},{"style":327},[12760],{"type":30,"value":12761},"objectStore",{"type":24,"tag":155,"props":12763,"children":12764},{"style":173},[12765],{"type":30,"value":551},{"type":24,"tag":155,"props":12767,"children":12768},{"style":479},[12769],{"type":30,"value":12719},{"type":24,"tag":155,"props":12771,"children":12772},{"style":173},[12773],{"type":30,"value":560},{"type":24,"tag":155,"props":12775,"children":12776},{"class":157,"line":252},[12777,12781,12786,12790,12794,12798,12803],{"type":24,"tag":155,"props":12778,"children":12779},{"style":237},[12780],{"type":30,"value":12688},{"type":24,"tag":155,"props":12782,"children":12783},{"style":479},[12784],{"type":30,"value":12785}," request",{"type":24,"tag":155,"props":12787,"children":12788},{"style":173},[12789],{"type":30,"value":443},{"type":24,"tag":155,"props":12791,"children":12792},{"style":327},[12793],{"type":30,"value":12626},{"type":24,"tag":155,"props":12795,"children":12796},{"style":173},[12797],{"type":30,"value":551},{"type":24,"tag":155,"props":12799,"children":12800},{"style":479},[12801],{"type":30,"value":12802},"store",{"type":24,"tag":155,"props":12804,"children":12805},{"style":173},[12806],{"type":30,"value":560},{"type":24,"tag":155,"props":12808,"children":12809},{"class":157,"line":260},[12810,12815,12819,12824,12828,12832,12836,12841,12845,12850,12854,12859],{"type":24,"tag":155,"props":12811,"children":12812},{"style":479},[12813],{"type":30,"value":12814},"        request",{"type":24,"tag":155,"props":12816,"children":12817},{"style":173},[12818],{"type":30,"value":176},{"type":24,"tag":155,"props":12820,"children":12821},{"style":327},[12822],{"type":30,"value":12823},"onsuccess",{"type":24,"tag":155,"props":12825,"children":12826},{"style":173},[12827],{"type":30,"value":443},{"type":24,"tag":155,"props":12829,"children":12830},{"style":173},[12831],{"type":30,"value":10060},{"type":24,"tag":155,"props":12833,"children":12834},{"style":173},[12835],{"type":30,"value":811},{"type":24,"tag":155,"props":12837,"children":12838},{"style":327},[12839],{"type":30,"value":12840}," resolve",{"type":24,"tag":155,"props":12842,"children":12843},{"style":173},[12844],{"type":30,"value":551},{"type":24,"tag":155,"props":12846,"children":12847},{"style":479},[12848],{"type":30,"value":12849},"request",{"type":24,"tag":155,"props":12851,"children":12852},{"style":173},[12853],{"type":30,"value":176},{"type":24,"tag":155,"props":12855,"children":12856},{"style":479},[12857],{"type":30,"value":12858},"result",{"type":24,"tag":155,"props":12860,"children":12861},{"style":173},[12862],{"type":30,"value":560},{"type":24,"tag":155,"props":12864,"children":12865},{"class":157,"line":294},[12866,12870,12874,12879,12883,12887,12891,12895,12899,12903,12907,12912],{"type":24,"tag":155,"props":12867,"children":12868},{"style":479},[12869],{"type":30,"value":12814},{"type":24,"tag":155,"props":12871,"children":12872},{"style":173},[12873],{"type":30,"value":176},{"type":24,"tag":155,"props":12875,"children":12876},{"style":327},[12877],{"type":30,"value":12878},"onerror",{"type":24,"tag":155,"props":12880,"children":12881},{"style":173},[12882],{"type":30,"value":443},{"type":24,"tag":155,"props":12884,"children":12885},{"style":173},[12886],{"type":30,"value":10060},{"type":24,"tag":155,"props":12888,"children":12889},{"style":173},[12890],{"type":30,"value":811},{"type":24,"tag":155,"props":12892,"children":12893},{"style":327},[12894],{"type":30,"value":12667},{"type":24,"tag":155,"props":12896,"children":12897},{"style":173},[12898],{"type":30,"value":551},{"type":24,"tag":155,"props":12900,"children":12901},{"style":479},[12902],{"type":30,"value":12849},{"type":24,"tag":155,"props":12904,"children":12905},{"style":173},[12906],{"type":30,"value":176},{"type":24,"tag":155,"props":12908,"children":12909},{"style":479},[12910],{"type":30,"value":12911},"error",{"type":24,"tag":155,"props":12913,"children":12914},{"style":173},[12915],{"type":30,"value":560},{"type":24,"tag":155,"props":12917,"children":12918},{"class":157,"line":303},[12919],{"type":24,"tag":155,"props":12920,"children":12921},{"style":173},[12922],{"type":30,"value":10004},{"type":24,"tag":32,"props":12924,"children":12925},{},[12926,12928,12933],{"type":30,"value":12927},"これを使うと ",{"type":24,"tag":46,"props":12929,"children":12931},{"className":12930},[],[12932],{"type":30,"value":12415},{"type":30,"value":12934}," は次の一行で済みます。",{"type":24,"tag":145,"props":12936,"children":12938},{"className":12585,"code":12937,"language":12587,"meta":8,"style":8},"async function setItem(musicId, pimData) {\n    // pimData は C# 側から Uint8Array として届く\n    await runTransaction(\"readwrite\", (store) => store.put(pimData, musicId));\n}\n",[12939],{"type":24,"tag":46,"props":12940,"children":12941},{"__ignoreMap":8},[12942,12984,12992,13073],{"type":24,"tag":155,"props":12943,"children":12944},{"class":157,"line":18},[12945,12949,12954,12959,12963,12968,12972,12976,12980],{"type":24,"tag":155,"props":12946,"children":12947},{"style":237},[12948],{"type":30,"value":9254},{"type":24,"tag":155,"props":12950,"children":12951},{"style":237},[12952],{"type":30,"value":12953}," function",{"type":24,"tag":155,"props":12955,"children":12956},{"style":327},[12957],{"type":30,"value":12958}," setItem",{"type":24,"tag":155,"props":12960,"children":12961},{"style":173},[12962],{"type":30,"value":551},{"type":24,"tag":155,"props":12964,"children":12965},{"style":479},[12966],{"type":30,"value":12967},"musicId",{"type":24,"tag":155,"props":12969,"children":12970},{"style":173},[12971],{"type":30,"value":396},{"type":24,"tag":155,"props":12973,"children":12974},{"style":479},[12975],{"type":30,"value":12343},{"type":24,"tag":155,"props":12977,"children":12978},{"style":173},[12979],{"type":30,"value":496},{"type":24,"tag":155,"props":12981,"children":12982},{"style":173},[12983],{"type":30,"value":12680},{"type":24,"tag":155,"props":12985,"children":12986},{"class":157,"line":189},[12987],{"type":24,"tag":155,"props":12988,"children":12989},{"style":1411},[12990],{"type":30,"value":12991},"    // pimData は C# 側から Uint8Array として届く\n",{"type":24,"tag":155,"props":12993,"children":12994},{"class":157,"line":223},[12995,12999,13003,13007,13011,13016,13020,13024,13028,13032,13036,13040,13044,13048,13053,13057,13061,13065,13069],{"type":24,"tag":155,"props":12996,"children":12997},{"style":161},[12998],{"type":30,"value":9426},{"type":24,"tag":155,"props":13000,"children":13001},{"style":327},[13002],{"type":30,"value":12604},{"type":24,"tag":155,"props":13004,"children":13005},{"style":173},[13006],{"type":30,"value":551},{"type":24,"tag":155,"props":13008,"children":13009},{"style":768},[13010],{"type":30,"value":2246},{"type":24,"tag":155,"props":13012,"children":13013},{"style":2239},[13014],{"type":30,"value":13015},"readwrite",{"type":24,"tag":155,"props":13017,"children":13018},{"style":768},[13019],{"type":30,"value":2246},{"type":24,"tag":155,"props":13021,"children":13022},{"style":173},[13023],{"type":30,"value":396},{"type":24,"tag":155,"props":13025,"children":13026},{"style":173},[13027],{"type":30,"value":476},{"type":24,"tag":155,"props":13029,"children":13030},{"style":479},[13031],{"type":30,"value":12802},{"type":24,"tag":155,"props":13033,"children":13034},{"style":173},[13035],{"type":30,"value":496},{"type":24,"tag":155,"props":13037,"children":13038},{"style":173},[13039],{"type":30,"value":811},{"type":24,"tag":155,"props":13041,"children":13042},{"style":479},[13043],{"type":30,"value":12744},{"type":24,"tag":155,"props":13045,"children":13046},{"style":173},[13047],{"type":30,"value":176},{"type":24,"tag":155,"props":13049,"children":13050},{"style":327},[13051],{"type":30,"value":13052},"put",{"type":24,"tag":155,"props":13054,"children":13055},{"style":173},[13056],{"type":30,"value":551},{"type":24,"tag":155,"props":13058,"children":13059},{"style":479},[13060],{"type":30,"value":12498},{"type":24,"tag":155,"props":13062,"children":13063},{"style":173},[13064],{"type":30,"value":396},{"type":24,"tag":155,"props":13066,"children":13067},{"style":479},[13068],{"type":30,"value":4224},{"type":24,"tag":155,"props":13070,"children":13071},{"style":173},[13072],{"type":30,"value":677},{"type":24,"tag":155,"props":13074,"children":13075},{"class":157,"line":233},[13076],{"type":24,"tag":155,"props":13077,"children":13078},{"style":173},[13079],{"type":30,"value":694},{"type":24,"tag":32,"props":13081,"children":13082},{},[13083],{"type":30,"value":13084},"たったこれだけです。実装の薄さが interop の真価だと感じる箇所です。",{"type":24,"tag":25,"props":13086,"children":13088},{"id":13087},"レイヤー5-indexeddbに書き込む",[13089],{"type":30,"value":13090},"レイヤー5 IndexedDBに書き込む",{"type":24,"tag":32,"props":13092,"children":13093},{},[13094,13096,13102,13104,13114],{"type":30,"value":13095},"最後のレイヤー、",{"type":24,"tag":46,"props":13097,"children":13099},{"className":13098},[],[13100],{"type":30,"value":13101},"store.put(pimData, musicId)",{"type":30,"value":13103}," が実際にIndexedDBへ書き込みます。ここで少し変わったのは、",{"type":24,"tag":38,"props":13105,"children":13106},{},[13107,13112],{"type":24,"tag":46,"props":13108,"children":13110},{"className":13109},[],[13111],{"type":30,"value":11111},{"type":30,"value":13113},"なしのobjectStore",{"type":30,"value":13115},"を使っている点です。",{"type":24,"tag":875,"props":13117,"children":13119},{"id":13118},"keypathなしのストア設計",[13120],{"type":30,"value":13121},"keyPathなしのストア設計",{"type":24,"tag":32,"props":13123,"children":13124},{},[13125,13131,13133,13138],{"type":24,"tag":46,"props":13126,"children":13128},{"className":13127},[],[13129],{"type":30,"value":13130},"createObjectStore",{"type":30,"value":13132}," を呼ぶ際、",{"type":24,"tag":46,"props":13134,"children":13136},{"className":13135},[],[13137],{"type":30,"value":11111},{"type":30,"value":13139}," を指定しない形で作っています。",{"type":24,"tag":145,"props":13141,"children":13143},{"className":12585,"code":13142,"language":12587,"meta":8,"style":8},"request.onupgradeneeded = (event) => {\n    const db = event.target.result;\n    if (!db.objectStoreNames.contains(MUSIC_STORE_NAME)) {\n        db.createObjectStore(MUSIC_STORE_NAME);\n    }\n};\n",[13144],{"type":24,"tag":46,"props":13145,"children":13146},{"__ignoreMap":8},[13147,13188,13229,13284,13312,13319],{"type":24,"tag":155,"props":13148,"children":13149},{"class":157,"line":18},[13150,13154,13158,13163,13167,13171,13176,13180,13184],{"type":24,"tag":155,"props":13151,"children":13152},{"style":479},[13153],{"type":30,"value":12849},{"type":24,"tag":155,"props":13155,"children":13156},{"style":173},[13157],{"type":30,"value":176},{"type":24,"tag":155,"props":13159,"children":13160},{"style":327},[13161],{"type":30,"value":13162},"onupgradeneeded",{"type":24,"tag":155,"props":13164,"children":13165},{"style":173},[13166],{"type":30,"value":443},{"type":24,"tag":155,"props":13168,"children":13169},{"style":173},[13170],{"type":30,"value":476},{"type":24,"tag":155,"props":13172,"children":13173},{"style":479},[13174],{"type":30,"value":13175},"event",{"type":24,"tag":155,"props":13177,"children":13178},{"style":173},[13179],{"type":30,"value":496},{"type":24,"tag":155,"props":13181,"children":13182},{"style":173},[13183],{"type":30,"value":811},{"type":24,"tag":155,"props":13185,"children":13186},{"style":173},[13187],{"type":30,"value":12680},{"type":24,"tag":155,"props":13189,"children":13190},{"class":157,"line":189},[13191,13196,13200,13204,13208,13212,13217,13221,13225],{"type":24,"tag":155,"props":13192,"children":13193},{"style":237},[13194],{"type":30,"value":13195},"    const",{"type":24,"tag":155,"props":13197,"children":13198},{"style":479},[13199],{"type":30,"value":12366},{"type":24,"tag":155,"props":13201,"children":13202},{"style":173},[13203],{"type":30,"value":443},{"type":24,"tag":155,"props":13205,"children":13206},{"style":479},[13207],{"type":30,"value":314},{"type":24,"tag":155,"props":13209,"children":13210},{"style":173},[13211],{"type":30,"value":176},{"type":24,"tag":155,"props":13213,"children":13214},{"style":479},[13215],{"type":30,"value":13216},"target",{"type":24,"tag":155,"props":13218,"children":13219},{"style":173},[13220],{"type":30,"value":176},{"type":24,"tag":155,"props":13222,"children":13223},{"style":479},[13224],{"type":30,"value":12858},{"type":24,"tag":155,"props":13226,"children":13227},{"style":173},[13228],{"type":30,"value":186},{"type":24,"tag":155,"props":13230,"children":13231},{"class":157,"line":223},[13232,13236,13240,13244,13249,13253,13258,13262,13267,13271,13275,13280],{"type":24,"tag":155,"props":13233,"children":13234},{"style":161},[13235],{"type":30,"value":1707},{"type":24,"tag":155,"props":13237,"children":13238},{"style":173},[13239],{"type":30,"value":476},{"type":24,"tag":155,"props":13241,"children":13242},{"style":237},[13243],{"type":30,"value":1716},{"type":24,"tag":155,"props":13245,"children":13246},{"style":479},[13247],{"type":30,"value":13248},"db",{"type":24,"tag":155,"props":13250,"children":13251},{"style":173},[13252],{"type":30,"value":176},{"type":24,"tag":155,"props":13254,"children":13255},{"style":479},[13256],{"type":30,"value":13257},"objectStoreNames",{"type":24,"tag":155,"props":13259,"children":13260},{"style":173},[13261],{"type":30,"value":176},{"type":24,"tag":155,"props":13263,"children":13264},{"style":327},[13265],{"type":30,"value":13266},"contains",{"type":24,"tag":155,"props":13268,"children":13269},{"style":173},[13270],{"type":30,"value":551},{"type":24,"tag":155,"props":13272,"children":13273},{"style":479},[13274],{"type":30,"value":12719},{"type":24,"tag":155,"props":13276,"children":13277},{"style":173},[13278],{"type":30,"value":13279},"))",{"type":24,"tag":155,"props":13281,"children":13282},{"style":173},[13283],{"type":30,"value":12680},{"type":24,"tag":155,"props":13285,"children":13286},{"class":157,"line":233},[13287,13292,13296,13300,13304,13308],{"type":24,"tag":155,"props":13288,"children":13289},{"style":479},[13290],{"type":30,"value":13291},"        db",{"type":24,"tag":155,"props":13293,"children":13294},{"style":173},[13295],{"type":30,"value":176},{"type":24,"tag":155,"props":13297,"children":13298},{"style":327},[13299],{"type":30,"value":13130},{"type":24,"tag":155,"props":13301,"children":13302},{"style":173},[13303],{"type":30,"value":551},{"type":24,"tag":155,"props":13305,"children":13306},{"style":479},[13307],{"type":30,"value":12719},{"type":24,"tag":155,"props":13309,"children":13310},{"style":173},[13311],{"type":30,"value":560},{"type":24,"tag":155,"props":13313,"children":13314},{"class":157,"line":252},[13315],{"type":24,"tag":155,"props":13316,"children":13317},{"style":173},[13318],{"type":30,"value":569},{"type":24,"tag":155,"props":13320,"children":13321},{"class":157,"line":260},[13322],{"type":24,"tag":155,"props":13323,"children":13324},{"style":173},[13325],{"type":30,"value":2278},{"type":24,"tag":32,"props":13327,"children":13328},{},[13329,13334,13336,13342],{"type":24,"tag":46,"props":13330,"children":13332},{"className":13331},[],[13333],{"type":30,"value":11111},{"type":30,"value":13335}," を指定すると、value に格納するオブジェクトの特定フィールドが自動でキーとして抽出される仕組みになります。多くの入門サンプルはこの方式で、",{"type":24,"tag":46,"props":13337,"children":13339},{"className":13338},[],[13340],{"type":30,"value":13341},"{ musicId: 1, pimData: ... }",{"type":30,"value":13343}," のようなラッパーオブジェクトを value に入れます。",{"type":24,"tag":32,"props":13345,"children":13346},{},[13347,13349,13354,13356,13362,13364,13369,13371,13376],{"type":30,"value":13348},"PICOMは",{"type":24,"tag":46,"props":13350,"children":13352},{"className":13351},[],[13353],{"type":30,"value":11111},{"type":30,"value":13355},"なしにしました。こうすると ",{"type":24,"tag":46,"props":13357,"children":13359},{"className":13358},[],[13360],{"type":30,"value":13361},"store.put(value, key)",{"type":30,"value":13363}," の第2引数でキーを外から明示的に渡す",{"type":24,"tag":38,"props":13365,"children":13366},{},[13367],{"type":30,"value":13368},"アウトオブラインキー",{"type":30,"value":13370},"方式になり、value には何でも置けます。PICOMの場合は生の ",{"type":24,"tag":46,"props":13372,"children":13374},{"className":13373},[],[13375],{"type":30,"value":11103},{"type":30,"value":13377}," を直接置いています。",{"type":24,"tag":907,"props":13379,"children":13382},{"cons-label":13380,"pros-label":13381},"keyPathありにしていたら得たもの","keyPathなし（アウトオブラインキー）の得",[13383,13419],{"type":24,"tag":913,"props":13384,"children":13385},{"v-slot:pros":8},[13386],{"type":24,"tag":917,"props":13387,"children":13388},{},[13389,13401,13414],{"type":24,"tag":921,"props":13390,"children":13391},{},[13392,13394,13399],{"type":30,"value":13393},"valueが任意の型でよい（",{"type":24,"tag":46,"props":13395,"children":13397},{"className":13396},[],[13398],{"type":30,"value":11103},{"type":30,"value":13400},"そのまま置ける）",{"type":24,"tag":921,"props":13402,"children":13403},{},[13404,13406,13412],{"type":30,"value":13405},"ラッパーオブジェクト ",{"type":24,"tag":46,"props":13407,"children":13409},{"className":13408},[],[13410],{"type":30,"value":13411},"{ musicId, pimData }",{"type":30,"value":13413}," のような構造を挟まずに済む",{"type":24,"tag":921,"props":13415,"children":13416},{},[13417],{"type":30,"value":13418},"PIMバイナリ内にIDが埋まっている現在の設計と相性が良い（二重にIDを持たない）",{"type":24,"tag":913,"props":13420,"children":13421},{"v-slot:cons":8},[13422,13435],{"type":24,"tag":921,"props":13423,"children":13424},{},[13425,13427,13433],{"type":30,"value":13426},"セカンダリインデックスを張れない（",{"type":24,"tag":46,"props":13428,"children":13430},{"className":13429},[],[13431],{"type":30,"value":13432},"store.createIndex('byTitle', 'title')",{"type":30,"value":13434}," のようなことができない）",{"type":24,"tag":921,"props":13436,"children":13437},{},[13438,13444,13446,13451],{"type":24,"tag":46,"props":13439,"children":13441},{"className":13440},[],[13442],{"type":30,"value":13443},"store.put(value)",{"type":30,"value":13445}," だけでなく必ず ",{"type":24,"tag":46,"props":13447,"children":13449},{"className":13448},[],[13450],{"type":30,"value":13361},{"type":30,"value":13452}," のようにキーを渡す必要がある",{"type":24,"tag":32,"props":13454,"children":13455},{},[13456],{"type":30,"value":13457},"セカンダリインデックスが必要になる場面、たとえば「タイトルで検索」のような機能を今後入れるなら、別ストアを並立させて軽量なメタデータだけを置くのがよいと考えています。現状は楽曲数が個人利用レベルなので、全件取得してC#側でフィルタで十分と判断しました。",{"type":24,"tag":875,"props":13459,"children":13461},{"id":13460},"storage-persistence-api",[13462],{"type":30,"value":13463},"Storage Persistence API",{"type":24,"tag":32,"props":13465,"children":13466},{},[13467],{"type":30,"value":13468},"IndexedDBは容量が逼迫するとブラウザが勝手にデータを消す仕様なので、ストアを開くタイミングで永続化をリクエストしています。",{"type":24,"tag":145,"props":13470,"children":13472},{"className":12585,"code":13471,"language":12587,"meta":8,"style":8},"if (navigator.storage && navigator.storage.persist) {\n    const isPersisted = await navigator.storage.persisted();\n    if (!isPersisted) {\n        const granted = await navigator.storage.persist();\n        console.log(`[Picom] Storage persistence ${granted ? 'granted' : 'denied'}`);\n    }\n}\n",[13473],{"type":24,"tag":46,"props":13474,"children":13475},{"__ignoreMap":8},[13476,13536,13581,13609,13653,13742,13749],{"type":24,"tag":155,"props":13477,"children":13478},{"class":157,"line":18},[13479,13484,13488,13493,13497,13502,13506,13511,13515,13519,13523,13528,13532],{"type":24,"tag":155,"props":13480,"children":13481},{"style":161},[13482],{"type":30,"value":13483},"if",{"type":24,"tag":155,"props":13485,"children":13486},{"style":173},[13487],{"type":30,"value":476},{"type":24,"tag":155,"props":13489,"children":13490},{"style":479},[13491],{"type":30,"value":13492},"navigator",{"type":24,"tag":155,"props":13494,"children":13495},{"style":173},[13496],{"type":30,"value":176},{"type":24,"tag":155,"props":13498,"children":13499},{"style":479},[13500],{"type":30,"value":13501},"storage",{"type":24,"tag":155,"props":13503,"children":13504},{"style":237},[13505],{"type":30,"value":1745},{"type":24,"tag":155,"props":13507,"children":13508},{"style":479},[13509],{"type":30,"value":13510}," navigator",{"type":24,"tag":155,"props":13512,"children":13513},{"style":173},[13514],{"type":30,"value":176},{"type":24,"tag":155,"props":13516,"children":13517},{"style":479},[13518],{"type":30,"value":13501},{"type":24,"tag":155,"props":13520,"children":13521},{"style":173},[13522],{"type":30,"value":176},{"type":24,"tag":155,"props":13524,"children":13525},{"style":479},[13526],{"type":30,"value":13527},"persist",{"type":24,"tag":155,"props":13529,"children":13530},{"style":173},[13531],{"type":30,"value":496},{"type":24,"tag":155,"props":13533,"children":13534},{"style":173},[13535],{"type":30,"value":12680},{"type":24,"tag":155,"props":13537,"children":13538},{"class":157,"line":189},[13539,13543,13548,13552,13556,13560,13564,13568,13572,13577],{"type":24,"tag":155,"props":13540,"children":13541},{"style":237},[13542],{"type":30,"value":13195},{"type":24,"tag":155,"props":13544,"children":13545},{"style":479},[13546],{"type":30,"value":13547}," isPersisted",{"type":24,"tag":155,"props":13549,"children":13550},{"style":173},[13551],{"type":30,"value":443},{"type":24,"tag":155,"props":13553,"children":13554},{"style":161},[13555],{"type":30,"value":12142},{"type":24,"tag":155,"props":13557,"children":13558},{"style":479},[13559],{"type":30,"value":13510},{"type":24,"tag":155,"props":13561,"children":13562},{"style":173},[13563],{"type":30,"value":176},{"type":24,"tag":155,"props":13565,"children":13566},{"style":479},[13567],{"type":30,"value":13501},{"type":24,"tag":155,"props":13569,"children":13570},{"style":173},[13571],{"type":30,"value":176},{"type":24,"tag":155,"props":13573,"children":13574},{"style":327},[13575],{"type":30,"value":13576},"persisted",{"type":24,"tag":155,"props":13578,"children":13579},{"style":173},[13580],{"type":30,"value":516},{"type":24,"tag":155,"props":13582,"children":13583},{"class":157,"line":223},[13584,13588,13592,13596,13601,13605],{"type":24,"tag":155,"props":13585,"children":13586},{"style":161},[13587],{"type":30,"value":1707},{"type":24,"tag":155,"props":13589,"children":13590},{"style":173},[13591],{"type":30,"value":476},{"type":24,"tag":155,"props":13593,"children":13594},{"style":237},[13595],{"type":30,"value":1716},{"type":24,"tag":155,"props":13597,"children":13598},{"style":479},[13599],{"type":30,"value":13600},"isPersisted",{"type":24,"tag":155,"props":13602,"children":13603},{"style":173},[13604],{"type":30,"value":496},{"type":24,"tag":155,"props":13606,"children":13607},{"style":173},[13608],{"type":30,"value":12680},{"type":24,"tag":155,"props":13610,"children":13611},{"class":157,"line":233},[13612,13616,13621,13625,13629,13633,13637,13641,13645,13649],{"type":24,"tag":155,"props":13613,"children":13614},{"style":237},[13615],{"type":30,"value":12688},{"type":24,"tag":155,"props":13617,"children":13618},{"style":479},[13619],{"type":30,"value":13620}," granted",{"type":24,"tag":155,"props":13622,"children":13623},{"style":173},[13624],{"type":30,"value":443},{"type":24,"tag":155,"props":13626,"children":13627},{"style":161},[13628],{"type":30,"value":12142},{"type":24,"tag":155,"props":13630,"children":13631},{"style":479},[13632],{"type":30,"value":13510},{"type":24,"tag":155,"props":13634,"children":13635},{"style":173},[13636],{"type":30,"value":176},{"type":24,"tag":155,"props":13638,"children":13639},{"style":479},[13640],{"type":30,"value":13501},{"type":24,"tag":155,"props":13642,"children":13643},{"style":173},[13644],{"type":30,"value":176},{"type":24,"tag":155,"props":13646,"children":13647},{"style":327},[13648],{"type":30,"value":13527},{"type":24,"tag":155,"props":13650,"children":13651},{"style":173},[13652],{"type":30,"value":516},{"type":24,"tag":155,"props":13654,"children":13655},{"class":157,"line":252},[13656,13661,13665,13670,13674,13679,13684,13689,13694,13698,13703,13708,13713,13717,13721,13726,13730,13734,13738],{"type":24,"tag":155,"props":13657,"children":13658},{"style":479},[13659],{"type":30,"value":13660},"        console",{"type":24,"tag":155,"props":13662,"children":13663},{"style":173},[13664],{"type":30,"value":176},{"type":24,"tag":155,"props":13666,"children":13667},{"style":327},[13668],{"type":30,"value":13669},"log",{"type":24,"tag":155,"props":13671,"children":13672},{"style":173},[13673],{"type":30,"value":551},{"type":24,"tag":155,"props":13675,"children":13676},{"style":768},[13677],{"type":30,"value":13678},"`",{"type":24,"tag":155,"props":13680,"children":13681},{"style":2239},[13682],{"type":30,"value":13683},"[Picom] Storage persistence ",{"type":24,"tag":155,"props":13685,"children":13686},{"style":161},[13687],{"type":30,"value":13688},"${",{"type":24,"tag":155,"props":13690,"children":13691},{"style":2239},[13692],{"type":30,"value":13693},"granted ",{"type":24,"tag":155,"props":13695,"children":13696},{"style":237},[13697],{"type":30,"value":324},{"type":24,"tag":155,"props":13699,"children":13700},{"style":768},[13701],{"type":30,"value":13702}," '",{"type":24,"tag":155,"props":13704,"children":13705},{"style":2239},[13706],{"type":30,"value":13707},"granted",{"type":24,"tag":155,"props":13709,"children":13710},{"style":768},[13711],{"type":30,"value":13712},"'",{"type":24,"tag":155,"props":13714,"children":13715},{"style":237},[13716],{"type":30,"value":286},{"type":24,"tag":155,"props":13718,"children":13719},{"style":768},[13720],{"type":30,"value":13702},{"type":24,"tag":155,"props":13722,"children":13723},{"style":2239},[13724],{"type":30,"value":13725},"denied",{"type":24,"tag":155,"props":13727,"children":13728},{"style":768},[13729],{"type":30,"value":13712},{"type":24,"tag":155,"props":13731,"children":13732},{"style":161},[13733],{"type":30,"value":4368},{"type":24,"tag":155,"props":13735,"children":13736},{"style":768},[13737],{"type":30,"value":13678},{"type":24,"tag":155,"props":13739,"children":13740},{"style":173},[13741],{"type":30,"value":560},{"type":24,"tag":155,"props":13743,"children":13744},{"class":157,"line":260},[13745],{"type":24,"tag":155,"props":13746,"children":13747},{"style":173},[13748],{"type":30,"value":569},{"type":24,"tag":155,"props":13750,"children":13751},{"class":157,"line":294},[13752],{"type":24,"tag":155,"props":13753,"children":13754},{"style":173},[13755],{"type":30,"value":694},{"type":24,"tag":32,"props":13757,"children":13758},{},[13759],{"type":30,"value":13760},"この処理を加えることで、容量逼迫時でも自動削除されなくなります。",{"type":24,"tag":2656,"props":13762,"children":13763},{},[13764],{"type":24,"tag":32,"props":13765,"children":13766},{},[13767],{"type":30,"value":13768},"ユーザーが明示的にサイトデータを削除すれば当然消えます。クラウド同期など別系統のバックアップは別途検討する価値があります。",{"type":24,"tag":25,"props":13770,"children":13772},{"id":13771},"読み込み側は逆順をたどる",[13773],{"type":30,"value":13771},{"type":24,"tag":32,"props":13775,"children":13776},{},[13777,13779,13784],{"type":30,"value":13778},"読み込みは、書き込みで通った5段階を",{"type":24,"tag":38,"props":13780,"children":13781},{},[13782],{"type":30,"value":13783},"そのまま逆向きに",{"type":30,"value":13785},"降りるだけです。",{"type":24,"tag":32,"props":13787,"children":13788},{},[13789,13790,13796,13797,13803,13805,13810],{"type":30,"value":12538},{"type":24,"tag":46,"props":13791,"children":13793},{"className":13792},[],[13794],{"type":30,"value":13795},"getAllItems",{"type":30,"value":11644},{"type":24,"tag":46,"props":13798,"children":13800},{"className":13799},[],[13801],{"type":30,"value":13802},"store.getAll()",{"type":30,"value":13804}," で値（",{"type":24,"tag":46,"props":13806,"children":13808},{"className":13807},[],[13809],{"type":30,"value":11103},{"type":30,"value":13811},"）の配列を一括取得します。PIM v3 からはIDがバイナリ内に埋め込まれているので、外側のキーを一緒に返す必要はありません。cursor で1件ずつ走査する必要もなく、一行で済みます。",{"type":24,"tag":145,"props":13813,"children":13815},{"className":12585,"code":13814,"language":12587,"meta":8,"style":8},"async function getAllItems() {\n    // PIM v3 からは ID がバイナリ内に埋め込まれているため、外側のキーは不要。\n    // store.getAll() で値（Uint8Array）配列を一括取得する。\n    return await runTransaction(\"readonly\", (store) => store.getAll());\n}\n",[13816],{"type":24,"tag":46,"props":13817,"children":13818},{"__ignoreMap":8},[13819,13843,13851,13859,13929],{"type":24,"tag":155,"props":13820,"children":13821},{"class":157,"line":18},[13822,13826,13830,13835,13839],{"type":24,"tag":155,"props":13823,"children":13824},{"style":237},[13825],{"type":30,"value":9254},{"type":24,"tag":155,"props":13827,"children":13828},{"style":237},[13829],{"type":30,"value":12953},{"type":24,"tag":155,"props":13831,"children":13832},{"style":327},[13833],{"type":30,"value":13834}," getAllItems",{"type":24,"tag":155,"props":13836,"children":13837},{"style":173},[13838],{"type":30,"value":6376},{"type":24,"tag":155,"props":13840,"children":13841},{"style":173},[13842],{"type":30,"value":12680},{"type":24,"tag":155,"props":13844,"children":13845},{"class":157,"line":189},[13846],{"type":24,"tag":155,"props":13847,"children":13848},{"style":1411},[13849],{"type":30,"value":13850},"    // PIM v3 からは ID がバイナリ内に埋め込まれているため、外側のキーは不要。\n",{"type":24,"tag":155,"props":13852,"children":13853},{"class":157,"line":223},[13854],{"type":24,"tag":155,"props":13855,"children":13856},{"style":1411},[13857],{"type":30,"value":13858},"    // store.getAll() で値（Uint8Array）配列を一括取得する。\n",{"type":24,"tag":155,"props":13860,"children":13861},{"class":157,"line":233},[13862,13866,13870,13874,13878,13882,13887,13891,13895,13899,13903,13907,13911,13915,13919,13924],{"type":24,"tag":155,"props":13863,"children":13864},{"style":161},[13865],{"type":30,"value":5045},{"type":24,"tag":155,"props":13867,"children":13868},{"style":161},[13869],{"type":30,"value":12142},{"type":24,"tag":155,"props":13871,"children":13872},{"style":327},[13873],{"type":30,"value":12604},{"type":24,"tag":155,"props":13875,"children":13876},{"style":173},[13877],{"type":30,"value":551},{"type":24,"tag":155,"props":13879,"children":13880},{"style":768},[13881],{"type":30,"value":2246},{"type":24,"tag":155,"props":13883,"children":13884},{"style":2239},[13885],{"type":30,"value":13886},"readonly",{"type":24,"tag":155,"props":13888,"children":13889},{"style":768},[13890],{"type":30,"value":2246},{"type":24,"tag":155,"props":13892,"children":13893},{"style":173},[13894],{"type":30,"value":396},{"type":24,"tag":155,"props":13896,"children":13897},{"style":173},[13898],{"type":30,"value":476},{"type":24,"tag":155,"props":13900,"children":13901},{"style":479},[13902],{"type":30,"value":12802},{"type":24,"tag":155,"props":13904,"children":13905},{"style":173},[13906],{"type":30,"value":496},{"type":24,"tag":155,"props":13908,"children":13909},{"style":173},[13910],{"type":30,"value":811},{"type":24,"tag":155,"props":13912,"children":13913},{"style":479},[13914],{"type":30,"value":12744},{"type":24,"tag":155,"props":13916,"children":13917},{"style":173},[13918],{"type":30,"value":176},{"type":24,"tag":155,"props":13920,"children":13921},{"style":327},[13922],{"type":30,"value":13923},"getAll",{"type":24,"tag":155,"props":13925,"children":13926},{"style":173},[13927],{"type":30,"value":13928},"());\n",{"type":24,"tag":155,"props":13930,"children":13931},{"class":157,"line":252},[13932],{"type":24,"tag":155,"props":13933,"children":13934},{"style":173},[13935],{"type":30,"value":694},{"type":24,"tag":32,"props":13937,"children":13938},{},[13939,13941,13947,13949,13955],{"type":30,"value":13940},"C#側は ",{"type":24,"tag":46,"props":13942,"children":13944},{"className":13943},[],[13945],{"type":30,"value":13946},"List\u003Cbyte[]>",{"type":30,"value":13948}," で受け取り、各バイナリを直接 ",{"type":24,"tag":46,"props":13950,"children":13952},{"className":13951},[],[13953],{"type":30,"value":13954},"MessagePackSerializer.Deserialize\u003CMusicDataModel>",{"type":30,"value":13956}," に渡します。",{"type":24,"tag":145,"props":13958,"children":13960},{"className":147,"code":13959,"language":149,"meta":8,"style":8},"public async Task\u003CList\u003CMusic>> ReadAllFromLocalStorageAsync()\n{\n    var stored = await _storage.GetAllAsync();\n    var musics = new List\u003CMusic>(stored.Count);\n\n    foreach (var bin in stored)\n    {\n        var (result, music) = Read(bin);\n        if (result is ReadResult.Success && music is not null)\n        {\n            musics.Add(music);\n        }\n    }\n\n    return musics;\n}\n",[13961],{"type":24,"tag":46,"props":13962,"children":13963},{"__ignoreMap":8},[13964,14009,14016,14054,14106,14113,14145,14152,14200,14258,14265,14294,14301,14308,14315,14330],{"type":24,"tag":155,"props":13965,"children":13966},{"class":157,"line":18},[13967,13971,13975,13979,13983,13987,13991,13995,14000,14005],{"type":24,"tag":155,"props":13968,"children":13969},{"style":237},[13970],{"type":30,"value":266},{"type":24,"tag":155,"props":13972,"children":13973},{"style":237},[13974],{"type":30,"value":10705},{"type":24,"tag":155,"props":13976,"children":13977},{"style":167},[13978],{"type":30,"value":9682},{"type":24,"tag":155,"props":13980,"children":13981},{"style":173},[13982],{"type":30,"value":366},{"type":24,"tag":155,"props":13984,"children":13985},{"style":167},[13986],{"type":30,"value":4868},{"type":24,"tag":155,"props":13988,"children":13989},{"style":173},[13990],{"type":30,"value":366},{"type":24,"tag":155,"props":13992,"children":13993},{"style":167},[13994],{"type":30,"value":2109},{"type":24,"tag":155,"props":13996,"children":13997},{"style":173},[13998],{"type":30,"value":13999},">>",{"type":24,"tag":155,"props":14001,"children":14002},{"style":327},[14003],{"type":30,"value":14004}," ReadAllFromLocalStorageAsync",{"type":24,"tag":155,"props":14006,"children":14007},{"style":173},[14008],{"type":30,"value":7082},{"type":24,"tag":155,"props":14010,"children":14011},{"class":157,"line":189},[14012],{"type":24,"tag":155,"props":14013,"children":14014},{"style":173},[14015],{"type":30,"value":300},{"type":24,"tag":155,"props":14017,"children":14018},{"class":157,"line":223},[14019,14023,14028,14032,14036,14041,14045,14050],{"type":24,"tag":155,"props":14020,"children":14021},{"style":237},[14022],{"type":30,"value":4904},{"type":24,"tag":155,"props":14024,"children":14025},{"style":327},[14026],{"type":30,"value":14027}," stored",{"type":24,"tag":155,"props":14029,"children":14030},{"style":173},[14031],{"type":30,"value":443},{"type":24,"tag":155,"props":14033,"children":14034},{"style":237},[14035],{"type":30,"value":12142},{"type":24,"tag":155,"props":14037,"children":14038},{"style":479},[14039],{"type":30,"value":14040}," _storage",{"type":24,"tag":155,"props":14042,"children":14043},{"style":173},[14044],{"type":30,"value":176},{"type":24,"tag":155,"props":14046,"children":14047},{"style":327},[14048],{"type":30,"value":14049},"GetAllAsync",{"type":24,"tag":155,"props":14051,"children":14052},{"style":173},[14053],{"type":30,"value":516},{"type":24,"tag":155,"props":14055,"children":14056},{"class":157,"line":233},[14057,14061,14065,14069,14073,14077,14081,14085,14089,14094,14098,14102],{"type":24,"tag":155,"props":14058,"children":14059},{"style":237},[14060],{"type":30,"value":4904},{"type":24,"tag":155,"props":14062,"children":14063},{"style":327},[14064],{"type":30,"value":4885},{"type":24,"tag":155,"props":14066,"children":14067},{"style":173},[14068],{"type":30,"value":443},{"type":24,"tag":155,"props":14070,"children":14071},{"style":237},[14072],{"type":30,"value":506},{"type":24,"tag":155,"props":14074,"children":14075},{"style":167},[14076],{"type":30,"value":8002},{"type":24,"tag":155,"props":14078,"children":14079},{"style":173},[14080],{"type":30,"value":366},{"type":24,"tag":155,"props":14082,"children":14083},{"style":167},[14084],{"type":30,"value":2109},{"type":24,"tag":155,"props":14086,"children":14087},{"style":173},[14088],{"type":30,"value":376},{"type":24,"tag":155,"props":14090,"children":14091},{"style":479},[14092],{"type":30,"value":14093},"stored",{"type":24,"tag":155,"props":14095,"children":14096},{"style":173},[14097],{"type":30,"value":176},{"type":24,"tag":155,"props":14099,"children":14100},{"style":479},[14101],{"type":30,"value":6832},{"type":24,"tag":155,"props":14103,"children":14104},{"style":173},[14105],{"type":30,"value":560},{"type":24,"tag":155,"props":14107,"children":14108},{"class":157,"line":252},[14109],{"type":24,"tag":155,"props":14110,"children":14111},{"emptyLinePlaceholder":227},[14112],{"type":30,"value":230},{"type":24,"tag":155,"props":14114,"children":14115},{"class":157,"line":260},[14116,14121,14125,14129,14133,14137,14141],{"type":24,"tag":155,"props":14117,"children":14118},{"style":161},[14119],{"type":30,"value":14120},"    foreach",{"type":24,"tag":155,"props":14122,"children":14123},{"style":173},[14124],{"type":30,"value":476},{"type":24,"tag":155,"props":14126,"children":14127},{"style":237},[14128],{"type":30,"value":2019},{"type":24,"tag":155,"props":14130,"children":14131},{"style":327},[14132],{"type":30,"value":11790},{"type":24,"tag":155,"props":14134,"children":14135},{"style":161},[14136],{"type":30,"value":7712},{"type":24,"tag":155,"props":14138,"children":14139},{"style":479},[14140],{"type":30,"value":14027},{"type":24,"tag":155,"props":14142,"children":14143},{"style":173},[14144],{"type":30,"value":453},{"type":24,"tag":155,"props":14146,"children":14147},{"class":157,"line":294},[14148],{"type":24,"tag":155,"props":14149,"children":14150},{"style":173},[14151],{"type":30,"value":462},{"type":24,"tag":155,"props":14153,"children":14154},{"class":157,"line":303},[14155,14159,14163,14167,14171,14175,14179,14183,14187,14191,14196],{"type":24,"tag":155,"props":14156,"children":14157},{"style":237},[14158],{"type":30,"value":7133},{"type":24,"tag":155,"props":14160,"children":14161},{"style":173},[14162],{"type":30,"value":476},{"type":24,"tag":155,"props":14164,"children":14165},{"style":327},[14166],{"type":30,"value":12858},{"type":24,"tag":155,"props":14168,"children":14169},{"style":173},[14170],{"type":30,"value":396},{"type":24,"tag":155,"props":14172,"children":14173},{"style":327},[14174],{"type":30,"value":4184},{"type":24,"tag":155,"props":14176,"children":14177},{"style":173},[14178],{"type":30,"value":496},{"type":24,"tag":155,"props":14180,"children":14181},{"style":173},[14182],{"type":30,"value":443},{"type":24,"tag":155,"props":14184,"children":14185},{"style":327},[14186],{"type":30,"value":4193},{"type":24,"tag":155,"props":14188,"children":14189},{"style":173},[14190],{"type":30,"value":551},{"type":24,"tag":155,"props":14192,"children":14193},{"style":479},[14194],{"type":30,"value":14195},"bin",{"type":24,"tag":155,"props":14197,"children":14198},{"style":173},[14199],{"type":30,"value":560},{"type":24,"tag":155,"props":14201,"children":14202},{"class":157,"line":337},[14203,14207,14211,14215,14219,14224,14228,14233,14237,14241,14245,14250,14254],{"type":24,"tag":155,"props":14204,"children":14205},{"style":161},[14206],{"type":30,"value":471},{"type":24,"tag":155,"props":14208,"children":14209},{"style":173},[14210],{"type":30,"value":476},{"type":24,"tag":155,"props":14212,"children":14213},{"style":479},[14214],{"type":30,"value":12858},{"type":24,"tag":155,"props":14216,"children":14217},{"style":237},[14218],{"type":30,"value":487},{"type":24,"tag":155,"props":14220,"children":14221},{"style":167},[14222],{"type":30,"value":14223}," ReadResult",{"type":24,"tag":155,"props":14225,"children":14226},{"style":173},[14227],{"type":30,"value":176},{"type":24,"tag":155,"props":14229,"children":14230},{"style":167},[14231],{"type":30,"value":14232},"Success",{"type":24,"tag":155,"props":14234,"children":14235},{"style":237},[14236],{"type":30,"value":1745},{"type":24,"tag":155,"props":14238,"children":14239},{"style":479},[14240],{"type":30,"value":4184},{"type":24,"tag":155,"props":14242,"children":14243},{"style":237},[14244],{"type":30,"value":487},{"type":24,"tag":155,"props":14246,"children":14247},{"style":237},[14248],{"type":30,"value":14249}," not",{"type":24,"tag":155,"props":14251,"children":14252},{"style":237},[14253],{"type":30,"value":448},{"type":24,"tag":155,"props":14255,"children":14256},{"style":173},[14257],{"type":30,"value":453},{"type":24,"tag":155,"props":14259,"children":14260},{"class":157,"line":345},[14261],{"type":24,"tag":155,"props":14262,"children":14263},{"style":173},[14264],{"type":30,"value":7748},{"type":24,"tag":155,"props":14266,"children":14267},{"class":157,"line":456},[14268,14273,14277,14282,14286,14290],{"type":24,"tag":155,"props":14269,"children":14270},{"style":479},[14271],{"type":30,"value":14272},"            musics",{"type":24,"tag":155,"props":14274,"children":14275},{"style":173},[14276],{"type":30,"value":176},{"type":24,"tag":155,"props":14278,"children":14279},{"style":327},[14280],{"type":30,"value":14281},"Add",{"type":24,"tag":155,"props":14283,"children":14284},{"style":173},[14285],{"type":30,"value":551},{"type":24,"tag":155,"props":14287,"children":14288},{"style":479},[14289],{"type":30,"value":2166},{"type":24,"tag":155,"props":14291,"children":14292},{"style":173},[14293],{"type":30,"value":560},{"type":24,"tag":155,"props":14295,"children":14296},{"class":157,"line":465},[14297],{"type":24,"tag":155,"props":14298,"children":14299},{"style":173},[14300],{"type":30,"value":7785},{"type":24,"tag":155,"props":14302,"children":14303},{"class":157,"line":519},[14304],{"type":24,"tag":155,"props":14305,"children":14306},{"style":173},[14307],{"type":30,"value":569},{"type":24,"tag":155,"props":14309,"children":14310},{"class":157,"line":540},[14311],{"type":24,"tag":155,"props":14312,"children":14313},{"emptyLinePlaceholder":227},[14314],{"type":30,"value":230},{"type":24,"tag":155,"props":14316,"children":14317},{"class":157,"line":563},[14318,14322,14326],{"type":24,"tag":155,"props":14319,"children":14320},{"style":161},[14321],{"type":30,"value":5045},{"type":24,"tag":155,"props":14323,"children":14324},{"style":479},[14325],{"type":30,"value":4885},{"type":24,"tag":155,"props":14327,"children":14328},{"style":173},[14329],{"type":30,"value":186},{"type":24,"tag":155,"props":14331,"children":14332},{"class":157,"line":572},[14333],{"type":24,"tag":155,"props":14334,"children":14335},{"style":173},[14336],{"type":30,"value":694},{"type":24,"tag":25,"props":14338,"children":14339},{"id":2679},[14340],{"type":30,"value":2679},{"type":24,"tag":917,"props":14342,"children":14343},{},[14344,14368,14380],{"type":24,"tag":921,"props":14345,"children":14346},{},[14347,14349,14354,14355,14360,14361,14366],{"type":30,"value":14348},".NET 6以降の ",{"type":24,"tag":46,"props":14350,"children":14352},{"className":14351},[],[14353],{"type":30,"value":11960},{"type":30,"value":11644},{"type":24,"tag":46,"props":14356,"children":14358},{"className":14357},[],[14359],{"type":30,"value":11095},{"type":30,"value":5150},{"type":24,"tag":46,"props":14362,"children":14364},{"className":14363},[],[14365],{"type":30,"value":11103},{"type":30,"value":14367}," に直接変換できるため、Base64で文字列化する必要はない",{"type":24,"tag":921,"props":14369,"children":14370},{},[14371,14373,14378],{"type":30,"value":14372},"IndexedDBは",{"type":24,"tag":46,"props":14374,"children":14376},{"className":14375},[],[14377],{"type":30,"value":11111},{"type":30,"value":14379},"なしで生のバイナリを直接入れる形が、自己完結したバイナリフォーマット（PIM）との相性が良い",{"type":24,"tag":921,"props":14381,"children":14382},{},[14383,14389],{"type":24,"tag":46,"props":14384,"children":14386},{"className":14385},[],[14387],{"type":30,"value":14388},"navigator.storage.persist()",{"type":30,"value":14390}," を忘れないこと",{"type":24,"tag":11867,"props":14392,"children":14394},{"label":14393,"to":2745},"PIMバイナリ側のフォーマット設計についてはこちら⬇️",[],{"type":24,"tag":2721,"props":14396,"children":14397},{},[14398],{"type":30,"value":2725},{"title":8,"searchDepth":189,"depth":189,"links":14400},[14401,14402,14403,14406,14407,14409,14410,14414,14415],{"id":27,"depth":189,"text":27},{"id":11116,"depth":189,"text":11116},{"id":11162,"depth":189,"text":11165,"children":14404},[14405],{"id":11676,"depth":223,"text":11676},{"id":11686,"depth":189,"text":11689},{"id":11873,"depth":189,"text":14408},"レイヤー3 C#/JS境界を越える — byteを直接渡す",{"id":12530,"depth":189,"text":12533},{"id":13087,"depth":189,"text":13090,"children":14411},[14412,14413],{"id":13118,"depth":223,"text":13121},{"id":13460,"depth":223,"text":13463},{"id":13771,"depth":189,"text":13771},{"id":2679,"depth":189,"text":2679},"content:articles:tech:blazor:indexeddb-blazor-interop.md","articles/tech/blazor/indexeddb-blazor-interop.md","articles/tech/blazor/indexeddb-blazor-interop",{"_path":14420,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":14421,"description":14422,"date":14423,"tags":14424,"rowTypeId":18,"sitemap":14428,"body":14429,"_type":2738,"_id":16489,"_source":2740,"_file":16490,"_stem":16491,"_extension":2743},"/articles/tech/blazor/soundmaker-wave-generation","SoundMakerでチップチューン音声パイプラインの作り方","自作OSSライブラリSoundMakerを使って、楽譜データからWAVバイナリを生成する音声合成パイプラインを解説します。サンプリングレートとテンポからサンプル位置を算出する基礎、バッファリング生成でBlazor WebAssemblyを固めない工夫、プレビュー音のキャッシュ戦略を紹介します。","2026-05-31",[13,14425,14426,14427,17],"音声合成","SoundMaker","OSS",{"loc":14420,"lastmod":14423,"priority":18},{"type":21,"children":14430,"toc":16479},[14431,14435,14454,14459,14466,14490,14495,14500,14578,14583,14589,14594,14739,14744,14750,14762,15085,15114,15127,15143,15149,15186,15705,15710,15716,15721,15748,15753,15757,16360,16371,16412,16418,16430,16435,16448,16452,16475],{"type":24,"tag":25,"props":14432,"children":14433},{"id":27},[14434],{"type":30,"value":27},{"type":24,"tag":32,"props":14436,"children":14437},{},[14438,14440,14446,14448,14453],{"type":30,"value":14439},"チップチューン音楽を作成できるWebアプリ「PICOM」では、",{"type":24,"tag":83,"props":14441,"children":14443},{"content":14442},"昔のゲーム機が出していた、ピコピコしたサウンドの総称",[14444],{"type":30,"value":14445},"チップチューン",{"type":30,"value":14447},"の音を自前で合成しています。その心臓部で使っているのが、私が以前から作っている自作OSSの",{"type":24,"tag":46,"props":14449,"children":14451},{"className":14450},[],[14452],{"type":30,"value":14426},{"type":30,"value":2846},{"type":24,"tag":32,"props":14455,"children":14456},{},[14457],{"type":30,"value":14458},"この記事では、楽譜データ（音符・休符・連符など）からPCM波形を生成し、最終的にWAVバイナリに落とすまでのパイプラインを紹介します。SoundMakerのドッグフーディングの話は以前書きましたが、今回は「実装そのもの」にフォーカスします。",{"type":24,"tag":14460,"props":14461,"children":14465},"external-link-card",{"description":14462,"image":14463,"title":14426,"to":14464},"8bit風サウンドを簡単に作成できる.NETライブラリ","/images/external/SoundMaker.jpg","https://github.com/AutumnSky1010/SoundMaker",[],{"type":24,"tag":92,"props":14467,"children":14468},{},[14469],{"type":24,"tag":32,"props":14470,"children":14471},{},[14472,14474,14480,14482,14488],{"type":30,"value":14473},"SoundMakerを使って楽譜データからPCM波形を合成する実装を紹介します。",{"type":24,"tag":46,"props":14475,"children":14477},{"className":14476},[],[14478],{"type":30,"value":14479},"FormatBuilder",{"type":30,"value":14481},"でサウンドフォーマットを構築し、",{"type":24,"tag":46,"props":14483,"children":14485},{"className":14484},[],[14486],{"type":30,"value":14487},"TrackBaseSound",{"type":30,"value":14489},"にトラックを載せ、バッファリング方式で1秒ごとにPCMチャンクを取り出してWAVバイナリに結合します。プレビュー用の単音はキャッシュすることで、ユーザーが音符を置くたびに再計算しない工夫も解説します。",{"type":24,"tag":25,"props":14491,"children":14493},{"id":14492},"パイプラインの全体像",[14494],{"type":30,"value":14492},{"type":24,"tag":32,"props":14496,"children":14497},{},[14498],{"type":30,"value":14499},"まずは全体の流れです。",{"type":24,"tag":4582,"props":14501,"children":14502},{},[14503,14513,14523,14557,14562,14573],{"type":24,"tag":921,"props":14504,"children":14505},{},[14506,14511],{"type":24,"tag":46,"props":14507,"children":14509},{"className":14508},[],[14510],{"type":30,"value":14479},{"type":30,"value":14512},"でサウンドフォーマット（サンプリングレート・ビット深度・チャンネル数）を構築",{"type":24,"tag":921,"props":14514,"children":14515},{},[14516,14521],{"type":24,"tag":46,"props":14517,"children":14519},{"className":14518},[],[14520],{"type":30,"value":14487},{"type":30,"value":14522},"を作り、各トラックの波形タイプ（矩形波・三角波・ノイズなど）を設定",{"type":24,"tag":921,"props":14524,"children":14525},{},[14526,14528,14534,14535,14541,14542,14548,14549,14555],{"type":30,"value":14527},"楽譜モデル（PICOMの",{"type":24,"tag":46,"props":14529,"children":14531},{"className":14530},[],[14532],{"type":30,"value":14533},"Note",{"type":30,"value":3458},{"type":24,"tag":46,"props":14536,"children":14538},{"className":14537},[],[14539],{"type":30,"value":14540},"Rest",{"type":30,"value":3458},{"type":24,"tag":46,"props":14543,"children":14545},{"className":14544},[],[14546],{"type":30,"value":14547},"Tie",{"type":30,"value":3458},{"type":24,"tag":46,"props":14550,"children":14552},{"className":14551},[],[14553],{"type":30,"value":14554},"Tuplet",{"type":30,"value":14556},"）をSoundMakerの音符オブジェクトに変換",{"type":24,"tag":921,"props":14558,"children":14559},{},[14560],{"type":30,"value":14561},"サンプル位置単位でPolyphonicTrackに音符を追加",{"type":24,"tag":921,"props":14563,"children":14564},{},[14565,14571],{"type":24,"tag":46,"props":14566,"children":14568},{"className":14567},[],[14569],{"type":30,"value":14570},"GenerateBufferedStereoWave",{"type":30,"value":14572},"でPCMチャンクをストリーム生成",{"type":24,"tag":921,"props":14574,"children":14575},{},[14576],{"type":30,"value":14577},"チャンクを連結してWAVファイルにラップ",{"type":24,"tag":32,"props":14579,"children":14580},{},[14581],{"type":30,"value":14582},"この流れをコードで見ていきます。",{"type":24,"tag":25,"props":14584,"children":14586},{"id":14585},"formatbuilderサウンドフォーマットの宣言",[14587],{"type":30,"value":14588},"FormatBuilder：サウンドフォーマットの宣言",{"type":24,"tag":32,"props":14590,"children":14591},{},[14592],{"type":30,"value":14593},"SoundMakerはビルダパターンでフォーマットを記述できます。PICOMは標準の44.1kHz / 16bit / ステレオで固定しています。",{"type":24,"tag":145,"props":14595,"children":14597},{"className":147,"code":14596,"language":149,"meta":8,"style":8},"private static FormatBuilder CreateFormatBuilder()\n{\n    return FormatBuilder.Create()\n        .WithFrequency(44100)\n        .WithBitDepth(16)\n        .WithChannelCount(2);\n}\n",[14598],{"type":24,"tag":46,"props":14599,"children":14600},{"__ignoreMap":8},[14601,14626,14633,14657,14683,14708,14732],{"type":24,"tag":155,"props":14602,"children":14603},{"class":157,"line":18},[14604,14608,14612,14617,14622],{"type":24,"tag":155,"props":14605,"children":14606},{"style":237},[14607],{"type":30,"value":1645},{"type":24,"tag":155,"props":14609,"children":14610},{"style":237},[14611],{"type":30,"value":11217},{"type":24,"tag":155,"props":14613,"children":14614},{"style":167},[14615],{"type":30,"value":14616}," FormatBuilder",{"type":24,"tag":155,"props":14618,"children":14619},{"style":327},[14620],{"type":30,"value":14621}," CreateFormatBuilder",{"type":24,"tag":155,"props":14623,"children":14624},{"style":173},[14625],{"type":30,"value":7082},{"type":24,"tag":155,"props":14627,"children":14628},{"class":157,"line":189},[14629],{"type":24,"tag":155,"props":14630,"children":14631},{"style":173},[14632],{"type":30,"value":300},{"type":24,"tag":155,"props":14634,"children":14635},{"class":157,"line":223},[14636,14640,14644,14648,14653],{"type":24,"tag":155,"props":14637,"children":14638},{"style":161},[14639],{"type":30,"value":5045},{"type":24,"tag":155,"props":14641,"children":14642},{"style":479},[14643],{"type":30,"value":14616},{"type":24,"tag":155,"props":14645,"children":14646},{"style":173},[14647],{"type":30,"value":176},{"type":24,"tag":155,"props":14649,"children":14650},{"style":327},[14651],{"type":30,"value":14652},"Create",{"type":24,"tag":155,"props":14654,"children":14655},{"style":173},[14656],{"type":30,"value":7082},{"type":24,"tag":155,"props":14658,"children":14659},{"class":157,"line":233},[14660,14665,14670,14674,14679],{"type":24,"tag":155,"props":14661,"children":14662},{"style":173},[14663],{"type":30,"value":14664},"        .",{"type":24,"tag":155,"props":14666,"children":14667},{"style":327},[14668],{"type":30,"value":14669},"WithFrequency",{"type":24,"tag":155,"props":14671,"children":14672},{"style":173},[14673],{"type":30,"value":551},{"type":24,"tag":155,"props":14675,"children":14676},{"style":3095},[14677],{"type":30,"value":14678},"44100",{"type":24,"tag":155,"props":14680,"children":14681},{"style":173},[14682],{"type":30,"value":453},{"type":24,"tag":155,"props":14684,"children":14685},{"class":157,"line":252},[14686,14690,14695,14699,14704],{"type":24,"tag":155,"props":14687,"children":14688},{"style":173},[14689],{"type":30,"value":14664},{"type":24,"tag":155,"props":14691,"children":14692},{"style":327},[14693],{"type":30,"value":14694},"WithBitDepth",{"type":24,"tag":155,"props":14696,"children":14697},{"style":173},[14698],{"type":30,"value":551},{"type":24,"tag":155,"props":14700,"children":14701},{"style":3095},[14702],{"type":30,"value":14703},"16",{"type":24,"tag":155,"props":14705,"children":14706},{"style":173},[14707],{"type":30,"value":453},{"type":24,"tag":155,"props":14709,"children":14710},{"class":157,"line":260},[14711,14715,14720,14724,14728],{"type":24,"tag":155,"props":14712,"children":14713},{"style":173},[14714],{"type":30,"value":14664},{"type":24,"tag":155,"props":14716,"children":14717},{"style":327},[14718],{"type":30,"value":14719},"WithChannelCount",{"type":24,"tag":155,"props":14721,"children":14722},{"style":173},[14723],{"type":30,"value":551},{"type":24,"tag":155,"props":14725,"children":14726},{"style":3095},[14727],{"type":30,"value":3268},{"type":24,"tag":155,"props":14729,"children":14730},{"style":173},[14731],{"type":30,"value":560},{"type":24,"tag":155,"props":14733,"children":14734},{"class":157,"line":294},[14735],{"type":24,"tag":155,"props":14736,"children":14737},{"style":173},[14738],{"type":30,"value":694},{"type":24,"tag":32,"props":14740,"children":14741},{},[14742],{"type":30,"value":14743},"ゆくゆくはこのパラメータもユーザ側で設定できるようにしようと考えています。",{"type":24,"tag":25,"props":14745,"children":14747},{"id":14746},"サンプル位置計算128分音符単位サンプル単位",[14748],{"type":30,"value":14749},"サンプル位置計算：128分音符単位→サンプル単位",{"type":24,"tag":32,"props":14751,"children":14752},{},[14753,14755,14760],{"type":30,"value":14754},"楽譜エディタ側では音符の位置を「128分音符何個目か」という整数で管理しています。これをSoundMakerに渡す時に、",{"type":24,"tag":38,"props":14756,"children":14757},{},[14758],{"type":30,"value":14759},"サンプル数単位",{"type":30,"value":14761},"に変換する必要があります。",{"type":24,"tag":145,"props":14763,"children":14765},{"className":147,"code":14764,"language":149,"meta":8,"style":8},"// 128分音符1つ分のサンプル数\nvar samplesPer128Note = (int)((int)soundFormat.SamplingFrequency * 60 / (tempo * 32d));\n\nforeach (var (position, component) in track.GetAllComponents())\n{\n    var smComponent = component.ToSoundMakerComponent();\n    SetVolumeTo(smComponent, track.Volume);\n    var samplePosition = samplesPer128Note * position;\n    polyphonicTrack.AddAt(samplePosition, smComponent);\n}\n",[14766],{"type":24,"tag":46,"props":14767,"children":14768},{"__ignoreMap":8},[14769,14777,14865,14872,14930,14937,14970,15008,15041,15078],{"type":24,"tag":155,"props":14770,"children":14771},{"class":157,"line":18},[14772],{"type":24,"tag":155,"props":14773,"children":14774},{"style":1411},[14775],{"type":30,"value":14776},"// 128分音符1つ分のサンプル数\n",{"type":24,"tag":155,"props":14778,"children":14779},{"class":157,"line":189},[14780,14784,14789,14793,14797,14801,14806,14810,14814,14819,14823,14828,14833,14838,14843,14847,14852,14856,14861],{"type":24,"tag":155,"props":14781,"children":14782},{"style":237},[14783],{"type":30,"value":2019},{"type":24,"tag":155,"props":14785,"children":14786},{"style":327},[14787],{"type":30,"value":14788}," samplesPer128Note",{"type":24,"tag":155,"props":14790,"children":14791},{"style":173},[14792],{"type":30,"value":443},{"type":24,"tag":155,"props":14794,"children":14795},{"style":173},[14796],{"type":30,"value":476},{"type":24,"tag":155,"props":14798,"children":14799},{"style":161},[14800],{"type":30,"value":6740},{"type":24,"tag":155,"props":14802,"children":14803},{"style":173},[14804],{"type":30,"value":14805},")((",{"type":24,"tag":155,"props":14807,"children":14808},{"style":161},[14809],{"type":30,"value":6740},{"type":24,"tag":155,"props":14811,"children":14812},{"style":173},[14813],{"type":30,"value":496},{"type":24,"tag":155,"props":14815,"children":14816},{"style":479},[14817],{"type":30,"value":14818},"soundFormat",{"type":24,"tag":155,"props":14820,"children":14821},{"style":173},[14822],{"type":30,"value":176},{"type":24,"tag":155,"props":14824,"children":14825},{"style":479},[14826],{"type":30,"value":14827},"SamplingFrequency",{"type":24,"tag":155,"props":14829,"children":14830},{"style":237},[14831],{"type":30,"value":14832}," *",{"type":24,"tag":155,"props":14834,"children":14835},{"style":3095},[14836],{"type":30,"value":14837}," 60",{"type":24,"tag":155,"props":14839,"children":14840},{"style":237},[14841],{"type":30,"value":14842}," /",{"type":24,"tag":155,"props":14844,"children":14845},{"style":173},[14846],{"type":30,"value":476},{"type":24,"tag":155,"props":14848,"children":14849},{"style":479},[14850],{"type":30,"value":14851},"tempo",{"type":24,"tag":155,"props":14853,"children":14854},{"style":237},[14855],{"type":30,"value":14832},{"type":24,"tag":155,"props":14857,"children":14858},{"style":3095},[14859],{"type":30,"value":14860}," 32d",{"type":24,"tag":155,"props":14862,"children":14863},{"style":173},[14864],{"type":30,"value":677},{"type":24,"tag":155,"props":14866,"children":14867},{"class":157,"line":223},[14868],{"type":24,"tag":155,"props":14869,"children":14870},{"emptyLinePlaceholder":227},[14871],{"type":30,"value":230},{"type":24,"tag":155,"props":14873,"children":14874},{"class":157,"line":233},[14875,14880,14884,14888,14892,14897,14901,14905,14909,14913,14917,14921,14926],{"type":24,"tag":155,"props":14876,"children":14877},{"style":161},[14878],{"type":30,"value":14879},"foreach",{"type":24,"tag":155,"props":14881,"children":14882},{"style":173},[14883],{"type":30,"value":476},{"type":24,"tag":155,"props":14885,"children":14886},{"style":237},[14887],{"type":30,"value":2019},{"type":24,"tag":155,"props":14889,"children":14890},{"style":173},[14891],{"type":30,"value":476},{"type":24,"tag":155,"props":14893,"children":14894},{"style":327},[14895],{"type":30,"value":14896},"position",{"type":24,"tag":155,"props":14898,"children":14899},{"style":173},[14900],{"type":30,"value":396},{"type":24,"tag":155,"props":14902,"children":14903},{"style":327},[14904],{"type":30,"value":6271},{"type":24,"tag":155,"props":14906,"children":14907},{"style":173},[14908],{"type":30,"value":496},{"type":24,"tag":155,"props":14910,"children":14911},{"style":161},[14912],{"type":30,"value":7712},{"type":24,"tag":155,"props":14914,"children":14915},{"style":479},[14916],{"type":30,"value":6245},{"type":24,"tag":155,"props":14918,"children":14919},{"style":173},[14920],{"type":30,"value":176},{"type":24,"tag":155,"props":14922,"children":14923},{"style":327},[14924],{"type":30,"value":14925},"GetAllComponents",{"type":24,"tag":155,"props":14927,"children":14928},{"style":173},[14929],{"type":30,"value":7740},{"type":24,"tag":155,"props":14931,"children":14932},{"class":157,"line":252},[14933],{"type":24,"tag":155,"props":14934,"children":14935},{"style":173},[14936],{"type":30,"value":300},{"type":24,"tag":155,"props":14938,"children":14939},{"class":157,"line":260},[14940,14944,14949,14953,14957,14961,14966],{"type":24,"tag":155,"props":14941,"children":14942},{"style":237},[14943],{"type":30,"value":4904},{"type":24,"tag":155,"props":14945,"children":14946},{"style":327},[14947],{"type":30,"value":14948}," smComponent",{"type":24,"tag":155,"props":14950,"children":14951},{"style":173},[14952],{"type":30,"value":443},{"type":24,"tag":155,"props":14954,"children":14955},{"style":479},[14956],{"type":30,"value":6271},{"type":24,"tag":155,"props":14958,"children":14959},{"style":173},[14960],{"type":30,"value":176},{"type":24,"tag":155,"props":14962,"children":14963},{"style":327},[14964],{"type":30,"value":14965},"ToSoundMakerComponent",{"type":24,"tag":155,"props":14967,"children":14968},{"style":173},[14969],{"type":30,"value":516},{"type":24,"tag":155,"props":14971,"children":14972},{"class":157,"line":294},[14973,14978,14982,14987,14991,14995,14999,15004],{"type":24,"tag":155,"props":14974,"children":14975},{"style":327},[14976],{"type":30,"value":14977},"    SetVolumeTo",{"type":24,"tag":155,"props":14979,"children":14980},{"style":173},[14981],{"type":30,"value":551},{"type":24,"tag":155,"props":14983,"children":14984},{"style":479},[14985],{"type":30,"value":14986},"smComponent",{"type":24,"tag":155,"props":14988,"children":14989},{"style":173},[14990],{"type":30,"value":396},{"type":24,"tag":155,"props":14992,"children":14993},{"style":479},[14994],{"type":30,"value":6245},{"type":24,"tag":155,"props":14996,"children":14997},{"style":173},[14998],{"type":30,"value":176},{"type":24,"tag":155,"props":15000,"children":15001},{"style":479},[15002],{"type":30,"value":15003},"Volume",{"type":24,"tag":155,"props":15005,"children":15006},{"style":173},[15007],{"type":30,"value":560},{"type":24,"tag":155,"props":15009,"children":15010},{"class":157,"line":303},[15011,15015,15020,15024,15028,15032,15037],{"type":24,"tag":155,"props":15012,"children":15013},{"style":237},[15014],{"type":30,"value":4904},{"type":24,"tag":155,"props":15016,"children":15017},{"style":327},[15018],{"type":30,"value":15019}," samplePosition",{"type":24,"tag":155,"props":15021,"children":15022},{"style":173},[15023],{"type":30,"value":443},{"type":24,"tag":155,"props":15025,"children":15026},{"style":479},[15027],{"type":30,"value":14788},{"type":24,"tag":155,"props":15029,"children":15030},{"style":237},[15031],{"type":30,"value":14832},{"type":24,"tag":155,"props":15033,"children":15034},{"style":479},[15035],{"type":30,"value":15036}," position",{"type":24,"tag":155,"props":15038,"children":15039},{"style":173},[15040],{"type":30,"value":186},{"type":24,"tag":155,"props":15042,"children":15043},{"class":157,"line":337},[15044,15049,15053,15057,15061,15066,15070,15074],{"type":24,"tag":155,"props":15045,"children":15046},{"style":479},[15047],{"type":30,"value":15048},"    polyphonicTrack",{"type":24,"tag":155,"props":15050,"children":15051},{"style":173},[15052],{"type":30,"value":176},{"type":24,"tag":155,"props":15054,"children":15055},{"style":327},[15056],{"type":30,"value":6393},{"type":24,"tag":155,"props":15058,"children":15059},{"style":173},[15060],{"type":30,"value":551},{"type":24,"tag":155,"props":15062,"children":15063},{"style":479},[15064],{"type":30,"value":15065},"samplePosition",{"type":24,"tag":155,"props":15067,"children":15068},{"style":173},[15069],{"type":30,"value":396},{"type":24,"tag":155,"props":15071,"children":15072},{"style":479},[15073],{"type":30,"value":14948},{"type":24,"tag":155,"props":15075,"children":15076},{"style":173},[15077],{"type":30,"value":560},{"type":24,"tag":155,"props":15079,"children":15080},{"class":157,"line":345},[15081],{"type":24,"tag":155,"props":15082,"children":15083},{"style":173},[15084],{"type":30,"value":694},{"type":24,"tag":32,"props":15086,"children":15087},{},[15088,15090,15096,15098,15104,15106,15112],{"type":30,"value":15089},"式の導出を簡単に。4分音符は1分間にtempo回ある（テンポの定義そのもの）ので、1つ分の時間は",{"type":24,"tag":46,"props":15091,"children":15093},{"className":15092},[],[15094],{"type":30,"value":15095},"60 / tempo",{"type":30,"value":15097},"秒。128分音符はその1/32なので",{"type":24,"tag":46,"props":15099,"children":15101},{"className":15100},[],[15102],{"type":30,"value":15103},"60 / (tempo * 32)",{"type":30,"value":15105},"秒。サンプル数は",{"type":24,"tag":46,"props":15107,"children":15109},{"className":15108},[],[15110],{"type":30,"value":15111},"samplingFrequency * 時間",{"type":30,"value":15113},"なので、掛け算すると上の式になります。",{"type":24,"tag":32,"props":15115,"children":15116},{},[15117,15119,15125],{"type":30,"value":15118},"たとえばtempo=120、44100Hzなら",{"type":24,"tag":46,"props":15120,"children":15122},{"className":15121},[],[15123],{"type":30,"value":15124},"samplesPer128Note ≈ 689",{"type":30,"value":15126},"。つまり128分音符1つは約689サンプル、という数字で扱います。",{"type":24,"tag":2656,"props":15128,"children":15129},{},[15130],{"type":24,"tag":32,"props":15131,"children":15132},{},[15133,15135,15141],{"type":30,"value":15134},"ここで浮動小数点を",{"type":24,"tag":46,"props":15136,"children":15138},{"className":15137},[],[15139],{"type":30,"value":15140},"(int)",{"type":30,"value":15142},"でキャストしている関係で、わずかにズレが蓄積します。PICOMでは1曲の長さが数分程度を想定しているので実害はありませんが、10分を超える曲では1サンプル分のズレが聴感上のチリッというノイズになることがあります。正確にやるなら累積誤差を各ステップで補正する方式が必要です。",{"type":24,"tag":25,"props":15144,"children":15146},{"id":15145},"波形タイプの分岐tracktypeごとに異なる音色",[15147],{"type":30,"value":15148},"波形タイプの分岐：Track.Typeごとに異なる音色",{"type":24,"tag":32,"props":15150,"children":15151},{},[15152,15154,15160,15162,15168,15170,15176,15178,15184],{"type":30,"value":15153},"チップチューンの音色は",{"type":24,"tag":83,"props":15155,"children":15157},{"content":15156},"矩形波。主旋律音源の代表格。パルス幅を変えると倍音の含み方が変わる",[15158],{"type":30,"value":15159},"Square Wave",{"type":30,"value":15161},"、",{"type":24,"tag":83,"props":15163,"children":15165},{"content":15164},"三角波。ベース音源として使われた。波形が角ばった三角形",[15166],{"type":30,"value":15167},"Triangle Wave",{"type":30,"value":15169},"、ノイズなど、決まったパターンがあります。PICOMでは",{"type":24,"tag":46,"props":15171,"children":15173},{"className":15172},[],[15174],{"type":30,"value":15175},"TrackTypes",{"type":30,"value":15177}," enumから",{"type":24,"tag":46,"props":15179,"children":15181},{"className":15180},[],[15182],{"type":30,"value":15183},"switch",{"type":30,"value":15185},"で分岐して波形オブジェクトを生成しています。",{"type":24,"tag":145,"props":15187,"children":15189},{"className":147,"code":15188,"language":149,"meta":8,"style":8},"var polyphonicTrack = track.Type switch\n{\n    TrackTypes.Square5   => trackBaseSound.CreatePolyphonicTrack(waveStartIndex, new SquareWave(SquareWaveRatio.Point5)),\n    TrackTypes.Square25  => trackBaseSound.CreatePolyphonicTrack(waveStartIndex, new SquareWave(SquareWaveRatio.Point25)),\n    TrackTypes.Square125 => trackBaseSound.CreatePolyphonicTrack(waveStartIndex, new SquareWave(SquareWaveRatio.Point125)),\n    TrackTypes.Triangle  => trackBaseSound.CreatePolyphonicTrack(waveStartIndex, new TriangleWave()),\n    TrackTypes.PseudoTriangle => trackBaseSound.CreatePolyphonicTrack(waveStartIndex, new PseudoTriangleWave()),\n    TrackTypes.LowbitNoise    => trackBaseSound.CreatePolyphonicTrack(waveStartIndex, new LowBitNoiseWave()),\n    _ => trackBaseSound.CreatePolyphonicTrack(waveStartIndex, new SquareWave(SquareWaveRatio.Point5))\n};\n",[15190],{"type":24,"tag":46,"props":15191,"children":15192},{"__ignoreMap":8},[15193,15226,15233,15314,15388,15461,15519,15576,15634,15698],{"type":24,"tag":155,"props":15194,"children":15195},{"class":157,"line":18},[15196,15200,15205,15209,15213,15217,15221],{"type":24,"tag":155,"props":15197,"children":15198},{"style":237},[15199],{"type":30,"value":2019},{"type":24,"tag":155,"props":15201,"children":15202},{"style":327},[15203],{"type":30,"value":15204}," polyphonicTrack",{"type":24,"tag":155,"props":15206,"children":15207},{"style":173},[15208],{"type":30,"value":443},{"type":24,"tag":155,"props":15210,"children":15211},{"style":479},[15212],{"type":30,"value":6245},{"type":24,"tag":155,"props":15214,"children":15215},{"style":173},[15216],{"type":30,"value":176},{"type":24,"tag":155,"props":15218,"children":15219},{"style":479},[15220],{"type":30,"value":3494},{"type":24,"tag":155,"props":15222,"children":15223},{"style":161},[15224],{"type":30,"value":15225}," switch\n",{"type":24,"tag":155,"props":15227,"children":15228},{"class":157,"line":189},[15229],{"type":24,"tag":155,"props":15230,"children":15231},{"style":173},[15232],{"type":30,"value":300},{"type":24,"tag":155,"props":15234,"children":15235},{"class":157,"line":223},[15236,15241,15245,15250,15255,15260,15264,15269,15273,15278,15282,15286,15291,15295,15300,15304,15309],{"type":24,"tag":155,"props":15237,"children":15238},{"style":167},[15239],{"type":30,"value":15240},"    TrackTypes",{"type":24,"tag":155,"props":15242,"children":15243},{"style":173},[15244],{"type":30,"value":176},{"type":24,"tag":155,"props":15246,"children":15247},{"style":167},[15248],{"type":30,"value":15249},"Square5",{"type":24,"tag":155,"props":15251,"children":15252},{"style":237},[15253],{"type":30,"value":15254},"   =>",{"type":24,"tag":155,"props":15256,"children":15257},{"style":479},[15258],{"type":30,"value":15259}," trackBaseSound",{"type":24,"tag":155,"props":15261,"children":15262},{"style":173},[15263],{"type":30,"value":176},{"type":24,"tag":155,"props":15265,"children":15266},{"style":327},[15267],{"type":30,"value":15268},"CreatePolyphonicTrack",{"type":24,"tag":155,"props":15270,"children":15271},{"style":173},[15272],{"type":30,"value":551},{"type":24,"tag":155,"props":15274,"children":15275},{"style":479},[15276],{"type":30,"value":15277},"waveStartIndex",{"type":24,"tag":155,"props":15279,"children":15280},{"style":173},[15281],{"type":30,"value":396},{"type":24,"tag":155,"props":15283,"children":15284},{"style":237},[15285],{"type":30,"value":506},{"type":24,"tag":155,"props":15287,"children":15288},{"style":167},[15289],{"type":30,"value":15290}," SquareWave",{"type":24,"tag":155,"props":15292,"children":15293},{"style":173},[15294],{"type":30,"value":551},{"type":24,"tag":155,"props":15296,"children":15297},{"style":479},[15298],{"type":30,"value":15299},"SquareWaveRatio",{"type":24,"tag":155,"props":15301,"children":15302},{"style":173},[15303],{"type":30,"value":176},{"type":24,"tag":155,"props":15305,"children":15306},{"style":479},[15307],{"type":30,"value":15308},"Point5",{"type":24,"tag":155,"props":15310,"children":15311},{"style":173},[15312],{"type":30,"value":15313},")),\n",{"type":24,"tag":155,"props":15315,"children":15316},{"class":157,"line":233},[15317,15321,15325,15330,15335,15339,15343,15347,15351,15355,15359,15363,15367,15371,15375,15379,15384],{"type":24,"tag":155,"props":15318,"children":15319},{"style":167},[15320],{"type":30,"value":15240},{"type":24,"tag":155,"props":15322,"children":15323},{"style":173},[15324],{"type":30,"value":176},{"type":24,"tag":155,"props":15326,"children":15327},{"style":167},[15328],{"type":30,"value":15329},"Square25",{"type":24,"tag":155,"props":15331,"children":15332},{"style":237},[15333],{"type":30,"value":15334},"  =>",{"type":24,"tag":155,"props":15336,"children":15337},{"style":479},[15338],{"type":30,"value":15259},{"type":24,"tag":155,"props":15340,"children":15341},{"style":173},[15342],{"type":30,"value":176},{"type":24,"tag":155,"props":15344,"children":15345},{"style":327},[15346],{"type":30,"value":15268},{"type":24,"tag":155,"props":15348,"children":15349},{"style":173},[15350],{"type":30,"value":551},{"type":24,"tag":155,"props":15352,"children":15353},{"style":479},[15354],{"type":30,"value":15277},{"type":24,"tag":155,"props":15356,"children":15357},{"style":173},[15358],{"type":30,"value":396},{"type":24,"tag":155,"props":15360,"children":15361},{"style":237},[15362],{"type":30,"value":506},{"type":24,"tag":155,"props":15364,"children":15365},{"style":167},[15366],{"type":30,"value":15290},{"type":24,"tag":155,"props":15368,"children":15369},{"style":173},[15370],{"type":30,"value":551},{"type":24,"tag":155,"props":15372,"children":15373},{"style":479},[15374],{"type":30,"value":15299},{"type":24,"tag":155,"props":15376,"children":15377},{"style":173},[15378],{"type":30,"value":176},{"type":24,"tag":155,"props":15380,"children":15381},{"style":479},[15382],{"type":30,"value":15383},"Point25",{"type":24,"tag":155,"props":15385,"children":15386},{"style":173},[15387],{"type":30,"value":15313},{"type":24,"tag":155,"props":15389,"children":15390},{"class":157,"line":252},[15391,15395,15399,15404,15408,15412,15416,15420,15424,15428,15432,15436,15440,15444,15448,15452,15457],{"type":24,"tag":155,"props":15392,"children":15393},{"style":167},[15394],{"type":30,"value":15240},{"type":24,"tag":155,"props":15396,"children":15397},{"style":173},[15398],{"type":30,"value":176},{"type":24,"tag":155,"props":15400,"children":15401},{"style":167},[15402],{"type":30,"value":15403},"Square125",{"type":24,"tag":155,"props":15405,"children":15406},{"style":237},[15407],{"type":30,"value":811},{"type":24,"tag":155,"props":15409,"children":15410},{"style":479},[15411],{"type":30,"value":15259},{"type":24,"tag":155,"props":15413,"children":15414},{"style":173},[15415],{"type":30,"value":176},{"type":24,"tag":155,"props":15417,"children":15418},{"style":327},[15419],{"type":30,"value":15268},{"type":24,"tag":155,"props":15421,"children":15422},{"style":173},[15423],{"type":30,"value":551},{"type":24,"tag":155,"props":15425,"children":15426},{"style":479},[15427],{"type":30,"value":15277},{"type":24,"tag":155,"props":15429,"children":15430},{"style":173},[15431],{"type":30,"value":396},{"type":24,"tag":155,"props":15433,"children":15434},{"style":237},[15435],{"type":30,"value":506},{"type":24,"tag":155,"props":15437,"children":15438},{"style":167},[15439],{"type":30,"value":15290},{"type":24,"tag":155,"props":15441,"children":15442},{"style":173},[15443],{"type":30,"value":551},{"type":24,"tag":155,"props":15445,"children":15446},{"style":479},[15447],{"type":30,"value":15299},{"type":24,"tag":155,"props":15449,"children":15450},{"style":173},[15451],{"type":30,"value":176},{"type":24,"tag":155,"props":15453,"children":15454},{"style":479},[15455],{"type":30,"value":15456},"Point125",{"type":24,"tag":155,"props":15458,"children":15459},{"style":173},[15460],{"type":30,"value":15313},{"type":24,"tag":155,"props":15462,"children":15463},{"class":157,"line":260},[15464,15468,15472,15477,15481,15485,15489,15493,15497,15501,15505,15509,15514],{"type":24,"tag":155,"props":15465,"children":15466},{"style":167},[15467],{"type":30,"value":15240},{"type":24,"tag":155,"props":15469,"children":15470},{"style":173},[15471],{"type":30,"value":176},{"type":24,"tag":155,"props":15473,"children":15474},{"style":167},[15475],{"type":30,"value":15476},"Triangle",{"type":24,"tag":155,"props":15478,"children":15479},{"style":237},[15480],{"type":30,"value":15334},{"type":24,"tag":155,"props":15482,"children":15483},{"style":479},[15484],{"type":30,"value":15259},{"type":24,"tag":155,"props":15486,"children":15487},{"style":173},[15488],{"type":30,"value":176},{"type":24,"tag":155,"props":15490,"children":15491},{"style":327},[15492],{"type":30,"value":15268},{"type":24,"tag":155,"props":15494,"children":15495},{"style":173},[15496],{"type":30,"value":551},{"type":24,"tag":155,"props":15498,"children":15499},{"style":479},[15500],{"type":30,"value":15277},{"type":24,"tag":155,"props":15502,"children":15503},{"style":173},[15504],{"type":30,"value":396},{"type":24,"tag":155,"props":15506,"children":15507},{"style":237},[15508],{"type":30,"value":506},{"type":24,"tag":155,"props":15510,"children":15511},{"style":167},[15512],{"type":30,"value":15513}," TriangleWave",{"type":24,"tag":155,"props":15515,"children":15516},{"style":173},[15517],{"type":30,"value":15518},"()),\n",{"type":24,"tag":155,"props":15520,"children":15521},{"class":157,"line":294},[15522,15526,15530,15535,15539,15543,15547,15551,15555,15559,15563,15567,15572],{"type":24,"tag":155,"props":15523,"children":15524},{"style":167},[15525],{"type":30,"value":15240},{"type":24,"tag":155,"props":15527,"children":15528},{"style":173},[15529],{"type":30,"value":176},{"type":24,"tag":155,"props":15531,"children":15532},{"style":167},[15533],{"type":30,"value":15534},"PseudoTriangle",{"type":24,"tag":155,"props":15536,"children":15537},{"style":237},[15538],{"type":30,"value":811},{"type":24,"tag":155,"props":15540,"children":15541},{"style":479},[15542],{"type":30,"value":15259},{"type":24,"tag":155,"props":15544,"children":15545},{"style":173},[15546],{"type":30,"value":176},{"type":24,"tag":155,"props":15548,"children":15549},{"style":327},[15550],{"type":30,"value":15268},{"type":24,"tag":155,"props":15552,"children":15553},{"style":173},[15554],{"type":30,"value":551},{"type":24,"tag":155,"props":15556,"children":15557},{"style":479},[15558],{"type":30,"value":15277},{"type":24,"tag":155,"props":15560,"children":15561},{"style":173},[15562],{"type":30,"value":396},{"type":24,"tag":155,"props":15564,"children":15565},{"style":237},[15566],{"type":30,"value":506},{"type":24,"tag":155,"props":15568,"children":15569},{"style":167},[15570],{"type":30,"value":15571}," PseudoTriangleWave",{"type":24,"tag":155,"props":15573,"children":15574},{"style":173},[15575],{"type":30,"value":15518},{"type":24,"tag":155,"props":15577,"children":15578},{"class":157,"line":303},[15579,15583,15587,15592,15597,15601,15605,15609,15613,15617,15621,15625,15630],{"type":24,"tag":155,"props":15580,"children":15581},{"style":167},[15582],{"type":30,"value":15240},{"type":24,"tag":155,"props":15584,"children":15585},{"style":173},[15586],{"type":30,"value":176},{"type":24,"tag":155,"props":15588,"children":15589},{"style":167},[15590],{"type":30,"value":15591},"LowbitNoise",{"type":24,"tag":155,"props":15593,"children":15594},{"style":237},[15595],{"type":30,"value":15596},"    =>",{"type":24,"tag":155,"props":15598,"children":15599},{"style":479},[15600],{"type":30,"value":15259},{"type":24,"tag":155,"props":15602,"children":15603},{"style":173},[15604],{"type":30,"value":176},{"type":24,"tag":155,"props":15606,"children":15607},{"style":327},[15608],{"type":30,"value":15268},{"type":24,"tag":155,"props":15610,"children":15611},{"style":173},[15612],{"type":30,"value":551},{"type":24,"tag":155,"props":15614,"children":15615},{"style":479},[15616],{"type":30,"value":15277},{"type":24,"tag":155,"props":15618,"children":15619},{"style":173},[15620],{"type":30,"value":396},{"type":24,"tag":155,"props":15622,"children":15623},{"style":237},[15624],{"type":30,"value":506},{"type":24,"tag":155,"props":15626,"children":15627},{"style":167},[15628],{"type":30,"value":15629}," LowBitNoiseWave",{"type":24,"tag":155,"props":15631,"children":15632},{"style":173},[15633],{"type":30,"value":15518},{"type":24,"tag":155,"props":15635,"children":15636},{"class":157,"line":337},[15637,15642,15646,15650,15654,15658,15662,15666,15670,15674,15678,15682,15686,15690,15694],{"type":24,"tag":155,"props":15638,"children":15639},{"style":648},[15640],{"type":30,"value":15641},"    _",{"type":24,"tag":155,"props":15643,"children":15644},{"style":237},[15645],{"type":30,"value":811},{"type":24,"tag":155,"props":15647,"children":15648},{"style":479},[15649],{"type":30,"value":15259},{"type":24,"tag":155,"props":15651,"children":15652},{"style":173},[15653],{"type":30,"value":176},{"type":24,"tag":155,"props":15655,"children":15656},{"style":327},[15657],{"type":30,"value":15268},{"type":24,"tag":155,"props":15659,"children":15660},{"style":173},[15661],{"type":30,"value":551},{"type":24,"tag":155,"props":15663,"children":15664},{"style":479},[15665],{"type":30,"value":15277},{"type":24,"tag":155,"props":15667,"children":15668},{"style":173},[15669],{"type":30,"value":396},{"type":24,"tag":155,"props":15671,"children":15672},{"style":237},[15673],{"type":30,"value":506},{"type":24,"tag":155,"props":15675,"children":15676},{"style":167},[15677],{"type":30,"value":15290},{"type":24,"tag":155,"props":15679,"children":15680},{"style":173},[15681],{"type":30,"value":551},{"type":24,"tag":155,"props":15683,"children":15684},{"style":479},[15685],{"type":30,"value":15299},{"type":24,"tag":155,"props":15687,"children":15688},{"style":173},[15689],{"type":30,"value":176},{"type":24,"tag":155,"props":15691,"children":15692},{"style":479},[15693],{"type":30,"value":15308},{"type":24,"tag":155,"props":15695,"children":15696},{"style":173},[15697],{"type":30,"value":10155},{"type":24,"tag":155,"props":15699,"children":15700},{"class":157,"line":345},[15701],{"type":24,"tag":155,"props":15702,"children":15703},{"style":173},[15704],{"type":30,"value":2278},{"type":24,"tag":32,"props":15706,"children":15707},{},[15708],{"type":30,"value":15709},"矩形波はデューティ比（パルス幅の比率）違いで3種類入れています。50% / 25% / 12.5%でそれぞれ倍音構成が変わるので、音色の違いが聞いてわかるはずです。",{"type":24,"tag":25,"props":15711,"children":15713},{"id":15712},"バッファリング生成wasmを固めないために",[15714],{"type":30,"value":15715},"バッファリング生成：WASMを固めないために",{"type":24,"tag":32,"props":15717,"children":15718},{},[15719],{"type":30,"value":15720},"PICOMの初期実装ではまず曲全体のPCMを一気に生成していたのですが、長い曲だとBlazor WebAssemblyのUIスレッドが秒単位で固まる現象に遭遇しました。",{"type":24,"tag":32,"props":15722,"children":15723},{},[15724,15726,15731,15733,15738,15740,15746],{"type":30,"value":15725},"解決策は",{"type":24,"tag":38,"props":15727,"children":15728},{},[15729],{"type":30,"value":15730},"バッファリング生成",{"type":30,"value":15732},"です。SoundMakerの",{"type":24,"tag":46,"props":15734,"children":15736},{"className":15735},[],[15737],{"type":30,"value":14570},{"type":30,"value":15739},"は、指定サンプル数（PICOMでは44100、つまり1秒分）ごとにPCMチャンクをyieldする",{"type":24,"tag":46,"props":15741,"children":15743},{"className":15742},[],[15744],{"type":30,"value":15745},"IEnumerable",{"type":30,"value":15747},"を返します。これを1個ずつ処理して連結することで、進捗表示も自然に実装できました。",{"type":24,"tag":32,"props":15749,"children":15750},{},[15751],{"type":30,"value":15752},"ちなみに、この方法だけでは完全に画面側のフリーズを解消することはできなかったため、マルチスレッド化を行うことで完全に解消できました。",{"type":24,"tag":11867,"props":15754,"children":15756},{"label":15755,"to":8773},"Blazor WASMマルチスレッド化の詳細はこちら⬇️",[],{"type":24,"tag":145,"props":15758,"children":15760},{"className":147,"code":15759,"language":149,"meta":8,"style":8},"var buffers = trackBaseSound.GenerateBufferedStereoWave(startIndex, 44100);\nvar pcmChunks = new List\u003Cbyte[]>();\nint totalPcmSize = 0;\nint chunkIndex = 0;\n\nvar totalSamples = trackBaseSound.GetAllTracks()\n    .Where(t => t.Count != 0)\n    .Max(t => t.EndIndex) - startIndex;\nint estimatedChunks = Math.Max(1, (int)Math.Ceiling(totalSamples / 44100.0));\n\nforeach (var wave in buffers)\n{\n    var chunk = wave.GetBytes(soundFormat.BitRate);\n    pcmChunks.Add(chunk);\n    totalPcmSize += chunk.Length;\n    chunkIndex++;\n    progress?.Report((double)chunkIndex / estimatedChunks);\n}\n",[15761],{"type":24,"tag":46,"props":15762,"children":15763},{"__ignoreMap":8},[15764,15814,15851,15875,15899,15906,15939,15989,16043,16132,16139,16171,16178,16228,16257,16285,16302,16353],{"type":24,"tag":155,"props":15765,"children":15766},{"class":157,"line":18},[15767,15771,15776,15780,15784,15788,15792,15796,15801,15805,15810],{"type":24,"tag":155,"props":15768,"children":15769},{"style":237},[15770],{"type":30,"value":2019},{"type":24,"tag":155,"props":15772,"children":15773},{"style":327},[15774],{"type":30,"value":15775}," buffers",{"type":24,"tag":155,"props":15777,"children":15778},{"style":173},[15779],{"type":30,"value":443},{"type":24,"tag":155,"props":15781,"children":15782},{"style":479},[15783],{"type":30,"value":15259},{"type":24,"tag":155,"props":15785,"children":15786},{"style":173},[15787],{"type":30,"value":176},{"type":24,"tag":155,"props":15789,"children":15790},{"style":327},[15791],{"type":30,"value":14570},{"type":24,"tag":155,"props":15793,"children":15794},{"style":173},[15795],{"type":30,"value":551},{"type":24,"tag":155,"props":15797,"children":15798},{"style":479},[15799],{"type":30,"value":15800},"startIndex",{"type":24,"tag":155,"props":15802,"children":15803},{"style":173},[15804],{"type":30,"value":396},{"type":24,"tag":155,"props":15806,"children":15807},{"style":3095},[15808],{"type":30,"value":15809}," 44100",{"type":24,"tag":155,"props":15811,"children":15812},{"style":173},[15813],{"type":30,"value":560},{"type":24,"tag":155,"props":15815,"children":15816},{"class":157,"line":189},[15817,15821,15826,15830,15834,15838,15842,15846],{"type":24,"tag":155,"props":15818,"children":15819},{"style":237},[15820],{"type":30,"value":2019},{"type":24,"tag":155,"props":15822,"children":15823},{"style":327},[15824],{"type":30,"value":15825}," pcmChunks",{"type":24,"tag":155,"props":15827,"children":15828},{"style":173},[15829],{"type":30,"value":443},{"type":24,"tag":155,"props":15831,"children":15832},{"style":237},[15833],{"type":30,"value":506},{"type":24,"tag":155,"props":15835,"children":15836},{"style":167},[15837],{"type":30,"value":8002},{"type":24,"tag":155,"props":15839,"children":15840},{"style":173},[15841],{"type":30,"value":366},{"type":24,"tag":155,"props":15843,"children":15844},{"style":161},[15845],{"type":30,"value":4202},{"type":24,"tag":155,"props":15847,"children":15848},{"style":173},[15849],{"type":30,"value":15850},"[]>();\n",{"type":24,"tag":155,"props":15852,"children":15853},{"class":157,"line":223},[15854,15858,15863,15867,15871],{"type":24,"tag":155,"props":15855,"children":15856},{"style":161},[15857],{"type":30,"value":6740},{"type":24,"tag":155,"props":15859,"children":15860},{"style":327},[15861],{"type":30,"value":15862}," totalPcmSize",{"type":24,"tag":155,"props":15864,"children":15865},{"style":173},[15866],{"type":30,"value":443},{"type":24,"tag":155,"props":15868,"children":15869},{"style":3095},[15870],{"type":30,"value":6842},{"type":24,"tag":155,"props":15872,"children":15873},{"style":173},[15874],{"type":30,"value":186},{"type":24,"tag":155,"props":15876,"children":15877},{"class":157,"line":233},[15878,15882,15887,15891,15895],{"type":24,"tag":155,"props":15879,"children":15880},{"style":161},[15881],{"type":30,"value":6740},{"type":24,"tag":155,"props":15883,"children":15884},{"style":327},[15885],{"type":30,"value":15886}," chunkIndex",{"type":24,"tag":155,"props":15888,"children":15889},{"style":173},[15890],{"type":30,"value":443},{"type":24,"tag":155,"props":15892,"children":15893},{"style":3095},[15894],{"type":30,"value":6842},{"type":24,"tag":155,"props":15896,"children":15897},{"style":173},[15898],{"type":30,"value":186},{"type":24,"tag":155,"props":15900,"children":15901},{"class":157,"line":252},[15902],{"type":24,"tag":155,"props":15903,"children":15904},{"emptyLinePlaceholder":227},[15905],{"type":30,"value":230},{"type":24,"tag":155,"props":15907,"children":15908},{"class":157,"line":260},[15909,15913,15918,15922,15926,15930,15935],{"type":24,"tag":155,"props":15910,"children":15911},{"style":237},[15912],{"type":30,"value":2019},{"type":24,"tag":155,"props":15914,"children":15915},{"style":327},[15916],{"type":30,"value":15917}," totalSamples",{"type":24,"tag":155,"props":15919,"children":15920},{"style":173},[15921],{"type":30,"value":443},{"type":24,"tag":155,"props":15923,"children":15924},{"style":479},[15925],{"type":30,"value":15259},{"type":24,"tag":155,"props":15927,"children":15928},{"style":173},[15929],{"type":30,"value":176},{"type":24,"tag":155,"props":15931,"children":15932},{"style":327},[15933],{"type":30,"value":15934},"GetAllTracks",{"type":24,"tag":155,"props":15936,"children":15937},{"style":173},[15938],{"type":30,"value":7082},{"type":24,"tag":155,"props":15940,"children":15941},{"class":157,"line":294},[15942,15947,15952,15956,15960,15964,15968,15972,15976,15981,15985],{"type":24,"tag":155,"props":15943,"children":15944},{"style":173},[15945],{"type":30,"value":15946},"    .",{"type":24,"tag":155,"props":15948,"children":15949},{"style":327},[15950],{"type":30,"value":15951},"Where",{"type":24,"tag":155,"props":15953,"children":15954},{"style":173},[15955],{"type":30,"value":551},{"type":24,"tag":155,"props":15957,"children":15958},{"style":327},[15959],{"type":30,"value":9861},{"type":24,"tag":155,"props":15961,"children":15962},{"style":237},[15963],{"type":30,"value":811},{"type":24,"tag":155,"props":15965,"children":15966},{"style":479},[15967],{"type":30,"value":9870},{"type":24,"tag":155,"props":15969,"children":15970},{"style":173},[15971],{"type":30,"value":176},{"type":24,"tag":155,"props":15973,"children":15974},{"style":479},[15975],{"type":30,"value":6832},{"type":24,"tag":155,"props":15977,"children":15978},{"style":237},[15979],{"type":30,"value":15980}," !=",{"type":24,"tag":155,"props":15982,"children":15983},{"style":3095},[15984],{"type":30,"value":6842},{"type":24,"tag":155,"props":15986,"children":15987},{"style":173},[15988],{"type":30,"value":453},{"type":24,"tag":155,"props":15990,"children":15991},{"class":157,"line":303},[15992,15996,16001,16005,16009,16013,16017,16021,16026,16030,16034,16039],{"type":24,"tag":155,"props":15993,"children":15994},{"style":173},[15995],{"type":30,"value":15946},{"type":24,"tag":155,"props":15997,"children":15998},{"style":327},[15999],{"type":30,"value":16000},"Max",{"type":24,"tag":155,"props":16002,"children":16003},{"style":173},[16004],{"type":30,"value":551},{"type":24,"tag":155,"props":16006,"children":16007},{"style":327},[16008],{"type":30,"value":9861},{"type":24,"tag":155,"props":16010,"children":16011},{"style":237},[16012],{"type":30,"value":811},{"type":24,"tag":155,"props":16014,"children":16015},{"style":479},[16016],{"type":30,"value":9870},{"type":24,"tag":155,"props":16018,"children":16019},{"style":173},[16020],{"type":30,"value":176},{"type":24,"tag":155,"props":16022,"children":16023},{"style":479},[16024],{"type":30,"value":16025},"EndIndex",{"type":24,"tag":155,"props":16027,"children":16028},{"style":173},[16029],{"type":30,"value":496},{"type":24,"tag":155,"props":16031,"children":16032},{"style":237},[16033],{"type":30,"value":7659},{"type":24,"tag":155,"props":16035,"children":16036},{"style":479},[16037],{"type":30,"value":16038}," startIndex",{"type":24,"tag":155,"props":16040,"children":16041},{"style":173},[16042],{"type":30,"value":186},{"type":24,"tag":155,"props":16044,"children":16045},{"class":157,"line":337},[16046,16050,16055,16059,16064,16068,16072,16076,16080,16084,16088,16092,16096,16101,16105,16110,16114,16119,16123,16128],{"type":24,"tag":155,"props":16047,"children":16048},{"style":161},[16049],{"type":30,"value":6740},{"type":24,"tag":155,"props":16051,"children":16052},{"style":327},[16053],{"type":30,"value":16054}," estimatedChunks",{"type":24,"tag":155,"props":16056,"children":16057},{"style":173},[16058],{"type":30,"value":443},{"type":24,"tag":155,"props":16060,"children":16061},{"style":479},[16062],{"type":30,"value":16063}," Math",{"type":24,"tag":155,"props":16065,"children":16066},{"style":173},[16067],{"type":30,"value":176},{"type":24,"tag":155,"props":16069,"children":16070},{"style":327},[16071],{"type":30,"value":16000},{"type":24,"tag":155,"props":16073,"children":16074},{"style":173},[16075],{"type":30,"value":551},{"type":24,"tag":155,"props":16077,"children":16078},{"style":3095},[16079],{"type":30,"value":3184},{"type":24,"tag":155,"props":16081,"children":16082},{"style":173},[16083],{"type":30,"value":396},{"type":24,"tag":155,"props":16085,"children":16086},{"style":173},[16087],{"type":30,"value":476},{"type":24,"tag":155,"props":16089,"children":16090},{"style":161},[16091],{"type":30,"value":6740},{"type":24,"tag":155,"props":16093,"children":16094},{"style":173},[16095],{"type":30,"value":496},{"type":24,"tag":155,"props":16097,"children":16098},{"style":479},[16099],{"type":30,"value":16100},"Math",{"type":24,"tag":155,"props":16102,"children":16103},{"style":173},[16104],{"type":30,"value":176},{"type":24,"tag":155,"props":16106,"children":16107},{"style":327},[16108],{"type":30,"value":16109},"Ceiling",{"type":24,"tag":155,"props":16111,"children":16112},{"style":173},[16113],{"type":30,"value":551},{"type":24,"tag":155,"props":16115,"children":16116},{"style":479},[16117],{"type":30,"value":16118},"totalSamples",{"type":24,"tag":155,"props":16120,"children":16121},{"style":237},[16122],{"type":30,"value":14842},{"type":24,"tag":155,"props":16124,"children":16125},{"style":3095},[16126],{"type":30,"value":16127}," 44100.0",{"type":24,"tag":155,"props":16129,"children":16130},{"style":173},[16131],{"type":30,"value":677},{"type":24,"tag":155,"props":16133,"children":16134},{"class":157,"line":345},[16135],{"type":24,"tag":155,"props":16136,"children":16137},{"emptyLinePlaceholder":227},[16138],{"type":30,"value":230},{"type":24,"tag":155,"props":16140,"children":16141},{"class":157,"line":456},[16142,16146,16150,16154,16159,16163,16167],{"type":24,"tag":155,"props":16143,"children":16144},{"style":161},[16145],{"type":30,"value":14879},{"type":24,"tag":155,"props":16147,"children":16148},{"style":173},[16149],{"type":30,"value":476},{"type":24,"tag":155,"props":16151,"children":16152},{"style":237},[16153],{"type":30,"value":2019},{"type":24,"tag":155,"props":16155,"children":16156},{"style":327},[16157],{"type":30,"value":16158}," wave",{"type":24,"tag":155,"props":16160,"children":16161},{"style":161},[16162],{"type":30,"value":7712},{"type":24,"tag":155,"props":16164,"children":16165},{"style":479},[16166],{"type":30,"value":15775},{"type":24,"tag":155,"props":16168,"children":16169},{"style":173},[16170],{"type":30,"value":453},{"type":24,"tag":155,"props":16172,"children":16173},{"class":157,"line":465},[16174],{"type":24,"tag":155,"props":16175,"children":16176},{"style":173},[16177],{"type":30,"value":300},{"type":24,"tag":155,"props":16179,"children":16180},{"class":157,"line":519},[16181,16185,16190,16194,16198,16202,16207,16211,16215,16219,16224],{"type":24,"tag":155,"props":16182,"children":16183},{"style":237},[16184],{"type":30,"value":4904},{"type":24,"tag":155,"props":16186,"children":16187},{"style":327},[16188],{"type":30,"value":16189}," chunk",{"type":24,"tag":155,"props":16191,"children":16192},{"style":173},[16193],{"type":30,"value":443},{"type":24,"tag":155,"props":16195,"children":16196},{"style":479},[16197],{"type":30,"value":16158},{"type":24,"tag":155,"props":16199,"children":16200},{"style":173},[16201],{"type":30,"value":176},{"type":24,"tag":155,"props":16203,"children":16204},{"style":327},[16205],{"type":30,"value":16206},"GetBytes",{"type":24,"tag":155,"props":16208,"children":16209},{"style":173},[16210],{"type":30,"value":551},{"type":24,"tag":155,"props":16212,"children":16213},{"style":479},[16214],{"type":30,"value":14818},{"type":24,"tag":155,"props":16216,"children":16217},{"style":173},[16218],{"type":30,"value":176},{"type":24,"tag":155,"props":16220,"children":16221},{"style":479},[16222],{"type":30,"value":16223},"BitRate",{"type":24,"tag":155,"props":16225,"children":16226},{"style":173},[16227],{"type":30,"value":560},{"type":24,"tag":155,"props":16229,"children":16230},{"class":157,"line":540},[16231,16236,16240,16244,16248,16253],{"type":24,"tag":155,"props":16232,"children":16233},{"style":479},[16234],{"type":30,"value":16235},"    pcmChunks",{"type":24,"tag":155,"props":16237,"children":16238},{"style":173},[16239],{"type":30,"value":176},{"type":24,"tag":155,"props":16241,"children":16242},{"style":327},[16243],{"type":30,"value":14281},{"type":24,"tag":155,"props":16245,"children":16246},{"style":173},[16247],{"type":30,"value":551},{"type":24,"tag":155,"props":16249,"children":16250},{"style":479},[16251],{"type":30,"value":16252},"chunk",{"type":24,"tag":155,"props":16254,"children":16255},{"style":173},[16256],{"type":30,"value":560},{"type":24,"tag":155,"props":16258,"children":16259},{"class":157,"line":563},[16260,16265,16269,16273,16277,16281],{"type":24,"tag":155,"props":16261,"children":16262},{"style":479},[16263],{"type":30,"value":16264},"    totalPcmSize",{"type":24,"tag":155,"props":16266,"children":16267},{"style":237},[16268],{"type":30,"value":1382},{"type":24,"tag":155,"props":16270,"children":16271},{"style":479},[16272],{"type":30,"value":16189},{"type":24,"tag":155,"props":16274,"children":16275},{"style":173},[16276],{"type":30,"value":176},{"type":24,"tag":155,"props":16278,"children":16279},{"style":479},[16280],{"type":30,"value":4399},{"type":24,"tag":155,"props":16282,"children":16283},{"style":173},[16284],{"type":30,"value":186},{"type":24,"tag":155,"props":16286,"children":16287},{"class":157,"line":572},[16288,16293,16298],{"type":24,"tag":155,"props":16289,"children":16290},{"style":479},[16291],{"type":30,"value":16292},"    chunkIndex",{"type":24,"tag":155,"props":16294,"children":16295},{"style":237},[16296],{"type":30,"value":16297},"++",{"type":24,"tag":155,"props":16299,"children":16300},{"style":173},[16301],{"type":30,"value":186},{"type":24,"tag":155,"props":16303,"children":16304},{"class":157,"line":580},[16305,16310,16314,16318,16323,16327,16332,16336,16341,16345,16349],{"type":24,"tag":155,"props":16306,"children":16307},{"style":479},[16308],{"type":30,"value":16309},"    progress",{"type":24,"tag":155,"props":16311,"children":16312},{"style":237},[16313],{"type":30,"value":324},{"type":24,"tag":155,"props":16315,"children":16316},{"style":173},[16317],{"type":30,"value":176},{"type":24,"tag":155,"props":16319,"children":16320},{"style":327},[16321],{"type":30,"value":16322},"Report",{"type":24,"tag":155,"props":16324,"children":16325},{"style":173},[16326],{"type":30,"value":12653},{"type":24,"tag":155,"props":16328,"children":16329},{"style":161},[16330],{"type":30,"value":16331},"double",{"type":24,"tag":155,"props":16333,"children":16334},{"style":173},[16335],{"type":30,"value":496},{"type":24,"tag":155,"props":16337,"children":16338},{"style":479},[16339],{"type":30,"value":16340},"chunkIndex",{"type":24,"tag":155,"props":16342,"children":16343},{"style":237},[16344],{"type":30,"value":14842},{"type":24,"tag":155,"props":16346,"children":16347},{"style":479},[16348],{"type":30,"value":16054},{"type":24,"tag":155,"props":16350,"children":16351},{"style":173},[16352],{"type":30,"value":560},{"type":24,"tag":155,"props":16354,"children":16355},{"class":157,"line":614},[16356],{"type":24,"tag":155,"props":16357,"children":16358},{"style":173},[16359],{"type":30,"value":694},{"type":24,"tag":32,"props":16361,"children":16362},{},[16363,16369],{"type":24,"tag":46,"props":16364,"children":16366},{"className":16365},[],[16367],{"type":30,"value":16368},"IProgress\u003Cdouble>",{"type":30,"value":16370},"で外から進捗を受け取れるようにしています。UI側ではこのコールバックでプログレスバーを動かします。",{"type":24,"tag":907,"props":16372,"children":16374},{"cons-label":16373,"pros-label":15730},"シンプルな一括生成",[16375,16396],{"type":24,"tag":913,"props":16376,"children":16377},{"v-slot:pros":8},[16378],{"type":24,"tag":917,"props":16379,"children":16380},{},[16381,16386,16391],{"type":24,"tag":921,"props":16382,"children":16383},{},[16384],{"type":30,"value":16385},"長い曲でもUIが固まらない",{"type":24,"tag":921,"props":16387,"children":16388},{},[16389],{"type":30,"value":16390},"進捗表示を自然に実装できる",{"type":24,"tag":921,"props":16392,"children":16393},{},[16394],{"type":30,"value":16395},"途中でキャンセル可能（PICOMではまだ未実装だが拡張しやすい）",{"type":24,"tag":913,"props":16397,"children":16398},{"v-slot:cons":8},[16399],{"type":24,"tag":917,"props":16400,"children":16401},{},[16402,16407],{"type":24,"tag":921,"props":16403,"children":16404},{},[16405],{"type":30,"value":16406},"コードが若干複雑になる",{"type":24,"tag":921,"props":16408,"children":16409},{},[16410],{"type":30,"value":16411},"チャンク境界での波形接続を考慮する必要がある",{"type":24,"tag":25,"props":16413,"children":16415},{"id":16414},"一番の収穫はsoundmakerのapiを自分でドッグフーディングできたこと",[16416],{"type":30,"value":16417},"一番の収穫はSoundMakerのAPIを自分でドッグフーディングできたこと",{"type":24,"tag":32,"props":16419,"children":16420},{},[16421,16423,16428],{"type":30,"value":16422},"今回の実装で一番の収穫は、自作ライブラリSoundMakerの",{"type":24,"tag":38,"props":16424,"children":16425},{},[16426],{"type":30,"value":16427},"API設計の粗さ",{"type":30,"value":16429},"に気付けたことです。以前の記事にも書きましたが、SoundMakerは最初に楽譜をそのままデータ構造にしてしまっていて、「音を配置する」という自然な操作がやや不自然な手順になっていました。",{"type":24,"tag":11867,"props":16431,"children":16434},{"label":16432,"to":16433},"SoundMakerのドッグフーディング体験の詳細はこちら⬇️","/articles/tech/development/oss-dogfooding",[],{"type":24,"tag":32,"props":16436,"children":16437},{},[16438,16440,16446],{"type":30,"value":16439},"PICOMで実際に使ってみると、たとえば音量設定を各コンポーネント（Note / Tie / Tuplet）に対して個別に行う必要があって、上の",{"type":24,"tag":46,"props":16441,"children":16443},{"className":16442},[],[16444],{"type":30,"value":16445},"SetVolumeTo",{"type":30,"value":16447},"のような再帰メソッドを自分で書く羽目になります。これはSoundMaker側に「PolyphonicTrackの全コンポーネントに一括で音量を設定する」APIがあれば解決する話で、ライブラリ側の改善点として積み残しています。",{"type":24,"tag":25,"props":16449,"children":16450},{"id":2679},[16451],{"type":30,"value":2679},{"type":24,"tag":917,"props":16453,"children":16454},{},[16455,16460,16465,16470],{"type":24,"tag":921,"props":16456,"children":16457},{},[16458],{"type":30,"value":16459},"サウンド生成パイプラインは「フォーマット構築 → トラック生成 → 音符追加 → バッファPCM生成 → WAVラップ」の5段階",{"type":24,"tag":921,"props":16461,"children":16462},{},[16463],{"type":30,"value":16464},"128分音符位置とサンプル位置の変換は、tempo × samplingFrequencyの式で導出できる",{"type":24,"tag":921,"props":16466,"children":16467},{},[16468],{"type":30,"value":16469},"Blazor WebAssemblyではバッファリング生成でないとUIスレッドが固まる",{"type":24,"tag":921,"props":16471,"children":16472},{},[16473],{"type":30,"value":16474},"自作OSSを自分で使うと、APIの粗さが必ず見えるはず！",{"type":24,"tag":2721,"props":16476,"children":16477},{},[16478],{"type":30,"value":2725},{"title":8,"searchDepth":189,"depth":189,"links":16480},[16481,16482,16483,16484,16485,16486,16487,16488],{"id":27,"depth":189,"text":27},{"id":14492,"depth":189,"text":14492},{"id":14585,"depth":189,"text":14588},{"id":14746,"depth":189,"text":14749},{"id":15145,"depth":189,"text":15148},{"id":15712,"depth":189,"text":15715},{"id":16414,"depth":189,"text":16417},{"id":2679,"depth":189,"text":2679},"content:articles:tech:blazor:soundmaker-wave-generation.md","articles/tech/blazor/soundmaker-wave-generation.md","articles/tech/blazor/soundmaker-wave-generation",1780243950515]