[{"data":1,"prerenderedAt":2196},["ShallowReactive",2],{"content-query-LPcYSijlTQ":3,"link-card-/articles/tech/blazor/blazor-wasm-multithreading":2186,"link-card-/articles/tech/development/oss-dogfooding":2189,"og-https://github.com/AutumnSky1010/SoundMaker":2192},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"date":10,"tags":11,"rowTypeId":17,"sitemap":18,"body":19,"_type":2180,"_id":2181,"_source":2182,"_file":2183,"_stem":2184,"_extension":2185},"/articles/tech/blazor/soundmaker-wave-generation","blazor",false,"","SoundMakerでチップチューン音声パイプラインの作り方","自作OSSライブラリSoundMakerを使って、楽譜データからWAVバイナリを生成する音声合成パイプラインを解説します。サンプリングレートとテンポからサンプル位置を算出する基礎、バッファリング生成でBlazor WebAssemblyを固めない工夫、プレビュー音のキャッシュ戦略を紹介します。","2026-05-31",[12,13,14,15,16],"C#","音声合成","SoundMaker","OSS","PICOM",1,{"loc":4,"lastmod":10,"priority":17},{"type":20,"children":21,"toc":2170},"root",[22,30,53,58,65,90,95,100,181,186,192,197,371,376,382,395,739,768,781,798,804,841,1366,1371,1377,1382,1409,1414,1420,2045,2056,2100,2106,2118,2123,2136,2141,2164],{"type":23,"tag":24,"props":25,"children":27},"element","h2",{"id":26},"はじめに",[28],{"type":29,"value":26},"text",{"type":23,"tag":31,"props":32,"children":33},"p",{},[34,36,43,45,51],{"type":29,"value":35},"チップチューン音楽を作成できるWebアプリ「PICOM」では、",{"type":23,"tag":37,"props":38,"children":40},"tooltip",{"content":39},"昔のゲーム機が出していた、ピコピコしたサウンドの総称",[41],{"type":29,"value":42},"チップチューン",{"type":29,"value":44},"の音を自前で合成しています。その心臓部で使っているのが、私が以前から作っている自作OSSの",{"type":23,"tag":46,"props":47,"children":49},"code",{"className":48},[],[50],{"type":29,"value":14},{"type":29,"value":52},"です。",{"type":23,"tag":31,"props":54,"children":55},{},[56],{"type":29,"value":57},"この記事では、楽譜データ（音符・休符・連符など）からPCM波形を生成し、最終的にWAVバイナリに落とすまでのパイプラインを紹介します。SoundMakerのドッグフーディングの話は以前書きましたが、今回は「実装そのもの」にフォーカスします。",{"type":23,"tag":59,"props":60,"children":64},"external-link-card",{"description":61,"image":62,"title":14,"to":63},"8bit風サウンドを簡単に作成できる.NETライブラリ","/images/external/SoundMaker.jpg","https://github.com/AutumnSky1010/SoundMaker",[],{"type":23,"tag":66,"props":67,"children":68},"summary-box",{},[69],{"type":23,"tag":31,"props":70,"children":71},{},[72,74,80,82,88],{"type":29,"value":73},"SoundMakerを使って楽譜データからPCM波形を合成する実装を紹介します。",{"type":23,"tag":46,"props":75,"children":77},{"className":76},[],[78],{"type":29,"value":79},"FormatBuilder",{"type":29,"value":81},"でサウンドフォーマットを構築し、",{"type":23,"tag":46,"props":83,"children":85},{"className":84},[],[86],{"type":29,"value":87},"TrackBaseSound",{"type":29,"value":89},"にトラックを載せ、バッファリング方式で1秒ごとにPCMチャンクを取り出してWAVバイナリに結合します。プレビュー用の単音はキャッシュすることで、ユーザーが音符を置くたびに再計算しない工夫も解説します。",{"type":23,"tag":24,"props":91,"children":93},{"id":92},"パイプラインの全体像",[94],{"type":29,"value":92},{"type":23,"tag":31,"props":96,"children":97},{},[98],{"type":29,"value":99},"まずは全体の流れです。",{"type":23,"tag":101,"props":102,"children":103},"ol",{},[104,115,125,160,165,176],{"type":23,"tag":105,"props":106,"children":107},"li",{},[108,113],{"type":23,"tag":46,"props":109,"children":111},{"className":110},[],[112],{"type":29,"value":79},{"type":29,"value":114},"でサウンドフォーマット（サンプリングレート・ビット深度・チャンネル数）を構築",{"type":23,"tag":105,"props":116,"children":117},{},[118,123],{"type":23,"tag":46,"props":119,"children":121},{"className":120},[],[122],{"type":29,"value":87},{"type":29,"value":124},"を作り、各トラックの波形タイプ（矩形波・三角波・ノイズなど）を設定",{"type":23,"tag":105,"props":126,"children":127},{},[128,130,136,138,144,145,151,152,158],{"type":29,"value":129},"楽譜モデル（PICOMの",{"type":23,"tag":46,"props":131,"children":133},{"className":132},[],[134],{"type":29,"value":135},"Note",{"type":29,"value":137}," / ",{"type":23,"tag":46,"props":139,"children":141},{"className":140},[],[142],{"type":29,"value":143},"Rest",{"type":29,"value":137},{"type":23,"tag":46,"props":146,"children":148},{"className":147},[],[149],{"type":29,"value":150},"Tie",{"type":29,"value":137},{"type":23,"tag":46,"props":153,"children":155},{"className":154},[],[156],{"type":29,"value":157},"Tuplet",{"type":29,"value":159},"）をSoundMakerの音符オブジェクトに変換",{"type":23,"tag":105,"props":161,"children":162},{},[163],{"type":29,"value":164},"サンプル位置単位でPolyphonicTrackに音符を追加",{"type":23,"tag":105,"props":166,"children":167},{},[168,174],{"type":23,"tag":46,"props":169,"children":171},{"className":170},[],[172],{"type":29,"value":173},"GenerateBufferedStereoWave",{"type":29,"value":175},"でPCMチャンクをストリーム生成",{"type":23,"tag":105,"props":177,"children":178},{},[179],{"type":29,"value":180},"チャンクを連結してWAVファイルにラップ",{"type":23,"tag":31,"props":182,"children":183},{},[184],{"type":29,"value":185},"この流れをコードで見ていきます。",{"type":23,"tag":24,"props":187,"children":189},{"id":188},"formatbuilderサウンドフォーマットの宣言",[190],{"type":29,"value":191},"FormatBuilder：サウンドフォーマットの宣言",{"type":23,"tag":31,"props":193,"children":194},{},[195],{"type":29,"value":196},"SoundMakerはビルダパターンでフォーマットを記述できます。PICOMは標準の44.1kHz / 16bit / ステレオで固定しています。",{"type":23,"tag":198,"props":199,"children":203},"pre",{"className":200,"code":201,"language":202,"meta":7,"style":7},"language-csharp shiki shiki-themes vitesse-dark","private static FormatBuilder CreateFormatBuilder()\n{\n    return FormatBuilder.Create()\n        .WithFrequency(44100)\n        .WithBitDepth(16)\n        .WithChannelCount(2);\n}\n","csharp",[204],{"type":23,"tag":46,"props":205,"children":206},{"__ignoreMap":7},[207,241,250,279,309,335,362],{"type":23,"tag":208,"props":209,"children":211},"span",{"class":210,"line":17},"line",[212,218,223,229,235],{"type":23,"tag":208,"props":213,"children":215},{"style":214},"--shiki-default:#CB7676",[216],{"type":29,"value":217},"private",{"type":23,"tag":208,"props":219,"children":220},{"style":214},[221],{"type":29,"value":222}," static",{"type":23,"tag":208,"props":224,"children":226},{"style":225},"--shiki-default:#5DA994",[227],{"type":29,"value":228}," FormatBuilder",{"type":23,"tag":208,"props":230,"children":232},{"style":231},"--shiki-default:#80A665",[233],{"type":29,"value":234}," CreateFormatBuilder",{"type":23,"tag":208,"props":236,"children":238},{"style":237},"--shiki-default:#666666",[239],{"type":29,"value":240},"()\n",{"type":23,"tag":208,"props":242,"children":244},{"class":210,"line":243},2,[245],{"type":23,"tag":208,"props":246,"children":247},{"style":237},[248],{"type":29,"value":249},"{\n",{"type":23,"tag":208,"props":251,"children":253},{"class":210,"line":252},3,[254,260,265,270,275],{"type":23,"tag":208,"props":255,"children":257},{"style":256},"--shiki-default:#4D9375",[258],{"type":29,"value":259},"    return",{"type":23,"tag":208,"props":261,"children":263},{"style":262},"--shiki-default:#BD976A",[264],{"type":29,"value":228},{"type":23,"tag":208,"props":266,"children":267},{"style":237},[268],{"type":29,"value":269},".",{"type":23,"tag":208,"props":271,"children":272},{"style":231},[273],{"type":29,"value":274},"Create",{"type":23,"tag":208,"props":276,"children":277},{"style":237},[278],{"type":29,"value":240},{"type":23,"tag":208,"props":280,"children":282},{"class":210,"line":281},4,[283,288,293,298,304],{"type":23,"tag":208,"props":284,"children":285},{"style":237},[286],{"type":29,"value":287},"        .",{"type":23,"tag":208,"props":289,"children":290},{"style":231},[291],{"type":29,"value":292},"WithFrequency",{"type":23,"tag":208,"props":294,"children":295},{"style":237},[296],{"type":29,"value":297},"(",{"type":23,"tag":208,"props":299,"children":301},{"style":300},"--shiki-default:#4C9A91",[302],{"type":29,"value":303},"44100",{"type":23,"tag":208,"props":305,"children":306},{"style":237},[307],{"type":29,"value":308},")\n",{"type":23,"tag":208,"props":310,"children":312},{"class":210,"line":311},5,[313,317,322,326,331],{"type":23,"tag":208,"props":314,"children":315},{"style":237},[316],{"type":29,"value":287},{"type":23,"tag":208,"props":318,"children":319},{"style":231},[320],{"type":29,"value":321},"WithBitDepth",{"type":23,"tag":208,"props":323,"children":324},{"style":237},[325],{"type":29,"value":297},{"type":23,"tag":208,"props":327,"children":328},{"style":300},[329],{"type":29,"value":330},"16",{"type":23,"tag":208,"props":332,"children":333},{"style":237},[334],{"type":29,"value":308},{"type":23,"tag":208,"props":336,"children":338},{"class":210,"line":337},6,[339,343,348,352,357],{"type":23,"tag":208,"props":340,"children":341},{"style":237},[342],{"type":29,"value":287},{"type":23,"tag":208,"props":344,"children":345},{"style":231},[346],{"type":29,"value":347},"WithChannelCount",{"type":23,"tag":208,"props":349,"children":350},{"style":237},[351],{"type":29,"value":297},{"type":23,"tag":208,"props":353,"children":354},{"style":300},[355],{"type":29,"value":356},"2",{"type":23,"tag":208,"props":358,"children":359},{"style":237},[360],{"type":29,"value":361},");\n",{"type":23,"tag":208,"props":363,"children":365},{"class":210,"line":364},7,[366],{"type":23,"tag":208,"props":367,"children":368},{"style":237},[369],{"type":29,"value":370},"}\n",{"type":23,"tag":31,"props":372,"children":373},{},[374],{"type":29,"value":375},"ゆくゆくはこのパラメータもユーザ側で設定できるようにしようと考えています。",{"type":23,"tag":24,"props":377,"children":379},{"id":378},"サンプル位置計算128分音符単位サンプル単位",[380],{"type":29,"value":381},"サンプル位置計算：128分音符単位→サンプル単位",{"type":23,"tag":31,"props":383,"children":384},{},[385,387,393],{"type":29,"value":386},"楽譜エディタ側では音符の位置を「128分音符何個目か」という整数で管理しています。これをSoundMakerに渡す時に、",{"type":23,"tag":388,"props":389,"children":390},"strong",{},[391],{"type":29,"value":392},"サンプル数単位",{"type":29,"value":394},"に変換する必要があります。",{"type":23,"tag":198,"props":396,"children":398},{"className":200,"code":397,"language":202,"meta":7,"style":7},"// 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",[399],{"type":23,"tag":46,"props":400,"children":401},{"__ignoreMap":7},[402,411,505,514,577,584,619,657,692,731],{"type":23,"tag":208,"props":403,"children":404},{"class":210,"line":17},[405],{"type":23,"tag":208,"props":406,"children":408},{"style":407},"--shiki-default:#758575DD",[409],{"type":29,"value":410},"// 128分音符1つ分のサンプル数\n",{"type":23,"tag":208,"props":412,"children":413},{"class":210,"line":243},[414,419,424,429,434,439,444,448,453,458,462,467,472,477,482,486,491,495,500],{"type":23,"tag":208,"props":415,"children":416},{"style":214},[417],{"type":29,"value":418},"var",{"type":23,"tag":208,"props":420,"children":421},{"style":231},[422],{"type":29,"value":423}," samplesPer128Note",{"type":23,"tag":208,"props":425,"children":426},{"style":237},[427],{"type":29,"value":428}," =",{"type":23,"tag":208,"props":430,"children":431},{"style":237},[432],{"type":29,"value":433}," (",{"type":23,"tag":208,"props":435,"children":436},{"style":256},[437],{"type":29,"value":438},"int",{"type":23,"tag":208,"props":440,"children":441},{"style":237},[442],{"type":29,"value":443},")((",{"type":23,"tag":208,"props":445,"children":446},{"style":256},[447],{"type":29,"value":438},{"type":23,"tag":208,"props":449,"children":450},{"style":237},[451],{"type":29,"value":452},")",{"type":23,"tag":208,"props":454,"children":455},{"style":262},[456],{"type":29,"value":457},"soundFormat",{"type":23,"tag":208,"props":459,"children":460},{"style":237},[461],{"type":29,"value":269},{"type":23,"tag":208,"props":463,"children":464},{"style":262},[465],{"type":29,"value":466},"SamplingFrequency",{"type":23,"tag":208,"props":468,"children":469},{"style":214},[470],{"type":29,"value":471}," *",{"type":23,"tag":208,"props":473,"children":474},{"style":300},[475],{"type":29,"value":476}," 60",{"type":23,"tag":208,"props":478,"children":479},{"style":214},[480],{"type":29,"value":481}," /",{"type":23,"tag":208,"props":483,"children":484},{"style":237},[485],{"type":29,"value":433},{"type":23,"tag":208,"props":487,"children":488},{"style":262},[489],{"type":29,"value":490},"tempo",{"type":23,"tag":208,"props":492,"children":493},{"style":214},[494],{"type":29,"value":471},{"type":23,"tag":208,"props":496,"children":497},{"style":300},[498],{"type":29,"value":499}," 32d",{"type":23,"tag":208,"props":501,"children":502},{"style":237},[503],{"type":29,"value":504},"));\n",{"type":23,"tag":208,"props":506,"children":507},{"class":210,"line":252},[508],{"type":23,"tag":208,"props":509,"children":511},{"emptyLinePlaceholder":510},true,[512],{"type":29,"value":513},"\n",{"type":23,"tag":208,"props":515,"children":516},{"class":210,"line":281},[517,522,526,530,534,539,544,549,553,558,563,567,572],{"type":23,"tag":208,"props":518,"children":519},{"style":256},[520],{"type":29,"value":521},"foreach",{"type":23,"tag":208,"props":523,"children":524},{"style":237},[525],{"type":29,"value":433},{"type":23,"tag":208,"props":527,"children":528},{"style":214},[529],{"type":29,"value":418},{"type":23,"tag":208,"props":531,"children":532},{"style":237},[533],{"type":29,"value":433},{"type":23,"tag":208,"props":535,"children":536},{"style":231},[537],{"type":29,"value":538},"position",{"type":23,"tag":208,"props":540,"children":541},{"style":237},[542],{"type":29,"value":543},",",{"type":23,"tag":208,"props":545,"children":546},{"style":231},[547],{"type":29,"value":548}," component",{"type":23,"tag":208,"props":550,"children":551},{"style":237},[552],{"type":29,"value":452},{"type":23,"tag":208,"props":554,"children":555},{"style":256},[556],{"type":29,"value":557}," in",{"type":23,"tag":208,"props":559,"children":560},{"style":262},[561],{"type":29,"value":562}," track",{"type":23,"tag":208,"props":564,"children":565},{"style":237},[566],{"type":29,"value":269},{"type":23,"tag":208,"props":568,"children":569},{"style":231},[570],{"type":29,"value":571},"GetAllComponents",{"type":23,"tag":208,"props":573,"children":574},{"style":237},[575],{"type":29,"value":576},"())\n",{"type":23,"tag":208,"props":578,"children":579},{"class":210,"line":311},[580],{"type":23,"tag":208,"props":581,"children":582},{"style":237},[583],{"type":29,"value":249},{"type":23,"tag":208,"props":585,"children":586},{"class":210,"line":337},[587,592,597,601,605,609,614],{"type":23,"tag":208,"props":588,"children":589},{"style":214},[590],{"type":29,"value":591},"    var",{"type":23,"tag":208,"props":593,"children":594},{"style":231},[595],{"type":29,"value":596}," smComponent",{"type":23,"tag":208,"props":598,"children":599},{"style":237},[600],{"type":29,"value":428},{"type":23,"tag":208,"props":602,"children":603},{"style":262},[604],{"type":29,"value":548},{"type":23,"tag":208,"props":606,"children":607},{"style":237},[608],{"type":29,"value":269},{"type":23,"tag":208,"props":610,"children":611},{"style":231},[612],{"type":29,"value":613},"ToSoundMakerComponent",{"type":23,"tag":208,"props":615,"children":616},{"style":237},[617],{"type":29,"value":618},"();\n",{"type":23,"tag":208,"props":620,"children":621},{"class":210,"line":364},[622,627,631,636,640,644,648,653],{"type":23,"tag":208,"props":623,"children":624},{"style":231},[625],{"type":29,"value":626},"    SetVolumeTo",{"type":23,"tag":208,"props":628,"children":629},{"style":237},[630],{"type":29,"value":297},{"type":23,"tag":208,"props":632,"children":633},{"style":262},[634],{"type":29,"value":635},"smComponent",{"type":23,"tag":208,"props":637,"children":638},{"style":237},[639],{"type":29,"value":543},{"type":23,"tag":208,"props":641,"children":642},{"style":262},[643],{"type":29,"value":562},{"type":23,"tag":208,"props":645,"children":646},{"style":237},[647],{"type":29,"value":269},{"type":23,"tag":208,"props":649,"children":650},{"style":262},[651],{"type":29,"value":652},"Volume",{"type":23,"tag":208,"props":654,"children":655},{"style":237},[656],{"type":29,"value":361},{"type":23,"tag":208,"props":658,"children":660},{"class":210,"line":659},8,[661,665,670,674,678,682,687],{"type":23,"tag":208,"props":662,"children":663},{"style":214},[664],{"type":29,"value":591},{"type":23,"tag":208,"props":666,"children":667},{"style":231},[668],{"type":29,"value":669}," samplePosition",{"type":23,"tag":208,"props":671,"children":672},{"style":237},[673],{"type":29,"value":428},{"type":23,"tag":208,"props":675,"children":676},{"style":262},[677],{"type":29,"value":423},{"type":23,"tag":208,"props":679,"children":680},{"style":214},[681],{"type":29,"value":471},{"type":23,"tag":208,"props":683,"children":684},{"style":262},[685],{"type":29,"value":686}," position",{"type":23,"tag":208,"props":688,"children":689},{"style":237},[690],{"type":29,"value":691},";\n",{"type":23,"tag":208,"props":693,"children":695},{"class":210,"line":694},9,[696,701,705,710,714,719,723,727],{"type":23,"tag":208,"props":697,"children":698},{"style":262},[699],{"type":29,"value":700},"    polyphonicTrack",{"type":23,"tag":208,"props":702,"children":703},{"style":237},[704],{"type":29,"value":269},{"type":23,"tag":208,"props":706,"children":707},{"style":231},[708],{"type":29,"value":709},"AddAt",{"type":23,"tag":208,"props":711,"children":712},{"style":237},[713],{"type":29,"value":297},{"type":23,"tag":208,"props":715,"children":716},{"style":262},[717],{"type":29,"value":718},"samplePosition",{"type":23,"tag":208,"props":720,"children":721},{"style":237},[722],{"type":29,"value":543},{"type":23,"tag":208,"props":724,"children":725},{"style":262},[726],{"type":29,"value":596},{"type":23,"tag":208,"props":728,"children":729},{"style":237},[730],{"type":29,"value":361},{"type":23,"tag":208,"props":732,"children":734},{"class":210,"line":733},10,[735],{"type":23,"tag":208,"props":736,"children":737},{"style":237},[738],{"type":29,"value":370},{"type":23,"tag":31,"props":740,"children":741},{},[742,744,750,752,758,760,766],{"type":29,"value":743},"式の導出を簡単に。4分音符は1分間にtempo回ある（テンポの定義そのもの）ので、1つ分の時間は",{"type":23,"tag":46,"props":745,"children":747},{"className":746},[],[748],{"type":29,"value":749},"60 / tempo",{"type":29,"value":751},"秒。128分音符はその1/32なので",{"type":23,"tag":46,"props":753,"children":755},{"className":754},[],[756],{"type":29,"value":757},"60 / (tempo * 32)",{"type":29,"value":759},"秒。サンプル数は",{"type":23,"tag":46,"props":761,"children":763},{"className":762},[],[764],{"type":29,"value":765},"samplingFrequency * 時間",{"type":29,"value":767},"なので、掛け算すると上の式になります。",{"type":23,"tag":31,"props":769,"children":770},{},[771,773,779],{"type":29,"value":772},"たとえばtempo=120、44100Hzなら",{"type":23,"tag":46,"props":774,"children":776},{"className":775},[],[777],{"type":29,"value":778},"samplesPer128Note ≈ 689",{"type":29,"value":780},"。つまり128分音符1つは約689サンプル、という数字で扱います。",{"type":23,"tag":782,"props":783,"children":784},"caution-box",{},[785],{"type":23,"tag":31,"props":786,"children":787},{},[788,790,796],{"type":29,"value":789},"ここで浮動小数点を",{"type":23,"tag":46,"props":791,"children":793},{"className":792},[],[794],{"type":29,"value":795},"(int)",{"type":29,"value":797},"でキャストしている関係で、わずかにズレが蓄積します。PICOMでは1曲の長さが数分程度を想定しているので実害はありませんが、10分を超える曲では1サンプル分のズレが聴感上のチリッというノイズになることがあります。正確にやるなら累積誤差を各ステップで補正する方式が必要です。",{"type":23,"tag":24,"props":799,"children":801},{"id":800},"波形タイプの分岐tracktypeごとに異なる音色",[802],{"type":29,"value":803},"波形タイプの分岐：Track.Typeごとに異なる音色",{"type":23,"tag":31,"props":805,"children":806},{},[807,809,815,817,823,825,831,833,839],{"type":29,"value":808},"チップチューンの音色は",{"type":23,"tag":37,"props":810,"children":812},{"content":811},"矩形波。主旋律音源の代表格。パルス幅を変えると倍音の含み方が変わる",[813],{"type":29,"value":814},"Square Wave",{"type":29,"value":816},"、",{"type":23,"tag":37,"props":818,"children":820},{"content":819},"三角波。ベース音源として使われた。波形が角ばった三角形",[821],{"type":29,"value":822},"Triangle Wave",{"type":29,"value":824},"、ノイズなど、決まったパターンがあります。PICOMでは",{"type":23,"tag":46,"props":826,"children":828},{"className":827},[],[829],{"type":29,"value":830},"TrackTypes",{"type":29,"value":832}," enumから",{"type":23,"tag":46,"props":834,"children":836},{"className":835},[],[837],{"type":29,"value":838},"switch",{"type":29,"value":840},"で分岐して波形オブジェクトを生成しています。",{"type":23,"tag":198,"props":842,"children":844},{"className":200,"code":843,"language":202,"meta":7,"style":7},"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",[845],{"type":23,"tag":46,"props":846,"children":847},{"__ignoreMap":7},[848,882,889,971,1045,1119,1177,1234,1292,1358],{"type":23,"tag":208,"props":849,"children":850},{"class":210,"line":17},[851,855,860,864,868,872,877],{"type":23,"tag":208,"props":852,"children":853},{"style":214},[854],{"type":29,"value":418},{"type":23,"tag":208,"props":856,"children":857},{"style":231},[858],{"type":29,"value":859}," polyphonicTrack",{"type":23,"tag":208,"props":861,"children":862},{"style":237},[863],{"type":29,"value":428},{"type":23,"tag":208,"props":865,"children":866},{"style":262},[867],{"type":29,"value":562},{"type":23,"tag":208,"props":869,"children":870},{"style":237},[871],{"type":29,"value":269},{"type":23,"tag":208,"props":873,"children":874},{"style":262},[875],{"type":29,"value":876},"Type",{"type":23,"tag":208,"props":878,"children":879},{"style":256},[880],{"type":29,"value":881}," switch\n",{"type":23,"tag":208,"props":883,"children":884},{"class":210,"line":243},[885],{"type":23,"tag":208,"props":886,"children":887},{"style":237},[888],{"type":29,"value":249},{"type":23,"tag":208,"props":890,"children":891},{"class":210,"line":252},[892,897,901,906,911,916,920,925,929,934,938,943,948,952,957,961,966],{"type":23,"tag":208,"props":893,"children":894},{"style":225},[895],{"type":29,"value":896},"    TrackTypes",{"type":23,"tag":208,"props":898,"children":899},{"style":237},[900],{"type":29,"value":269},{"type":23,"tag":208,"props":902,"children":903},{"style":225},[904],{"type":29,"value":905},"Square5",{"type":23,"tag":208,"props":907,"children":908},{"style":214},[909],{"type":29,"value":910},"   =>",{"type":23,"tag":208,"props":912,"children":913},{"style":262},[914],{"type":29,"value":915}," trackBaseSound",{"type":23,"tag":208,"props":917,"children":918},{"style":237},[919],{"type":29,"value":269},{"type":23,"tag":208,"props":921,"children":922},{"style":231},[923],{"type":29,"value":924},"CreatePolyphonicTrack",{"type":23,"tag":208,"props":926,"children":927},{"style":237},[928],{"type":29,"value":297},{"type":23,"tag":208,"props":930,"children":931},{"style":262},[932],{"type":29,"value":933},"waveStartIndex",{"type":23,"tag":208,"props":935,"children":936},{"style":237},[937],{"type":29,"value":543},{"type":23,"tag":208,"props":939,"children":940},{"style":214},[941],{"type":29,"value":942}," new",{"type":23,"tag":208,"props":944,"children":945},{"style":225},[946],{"type":29,"value":947}," SquareWave",{"type":23,"tag":208,"props":949,"children":950},{"style":237},[951],{"type":29,"value":297},{"type":23,"tag":208,"props":953,"children":954},{"style":262},[955],{"type":29,"value":956},"SquareWaveRatio",{"type":23,"tag":208,"props":958,"children":959},{"style":237},[960],{"type":29,"value":269},{"type":23,"tag":208,"props":962,"children":963},{"style":262},[964],{"type":29,"value":965},"Point5",{"type":23,"tag":208,"props":967,"children":968},{"style":237},[969],{"type":29,"value":970},")),\n",{"type":23,"tag":208,"props":972,"children":973},{"class":210,"line":281},[974,978,982,987,992,996,1000,1004,1008,1012,1016,1020,1024,1028,1032,1036,1041],{"type":23,"tag":208,"props":975,"children":976},{"style":225},[977],{"type":29,"value":896},{"type":23,"tag":208,"props":979,"children":980},{"style":237},[981],{"type":29,"value":269},{"type":23,"tag":208,"props":983,"children":984},{"style":225},[985],{"type":29,"value":986},"Square25",{"type":23,"tag":208,"props":988,"children":989},{"style":214},[990],{"type":29,"value":991},"  =>",{"type":23,"tag":208,"props":993,"children":994},{"style":262},[995],{"type":29,"value":915},{"type":23,"tag":208,"props":997,"children":998},{"style":237},[999],{"type":29,"value":269},{"type":23,"tag":208,"props":1001,"children":1002},{"style":231},[1003],{"type":29,"value":924},{"type":23,"tag":208,"props":1005,"children":1006},{"style":237},[1007],{"type":29,"value":297},{"type":23,"tag":208,"props":1009,"children":1010},{"style":262},[1011],{"type":29,"value":933},{"type":23,"tag":208,"props":1013,"children":1014},{"style":237},[1015],{"type":29,"value":543},{"type":23,"tag":208,"props":1017,"children":1018},{"style":214},[1019],{"type":29,"value":942},{"type":23,"tag":208,"props":1021,"children":1022},{"style":225},[1023],{"type":29,"value":947},{"type":23,"tag":208,"props":1025,"children":1026},{"style":237},[1027],{"type":29,"value":297},{"type":23,"tag":208,"props":1029,"children":1030},{"style":262},[1031],{"type":29,"value":956},{"type":23,"tag":208,"props":1033,"children":1034},{"style":237},[1035],{"type":29,"value":269},{"type":23,"tag":208,"props":1037,"children":1038},{"style":262},[1039],{"type":29,"value":1040},"Point25",{"type":23,"tag":208,"props":1042,"children":1043},{"style":237},[1044],{"type":29,"value":970},{"type":23,"tag":208,"props":1046,"children":1047},{"class":210,"line":311},[1048,1052,1056,1061,1066,1070,1074,1078,1082,1086,1090,1094,1098,1102,1106,1110,1115],{"type":23,"tag":208,"props":1049,"children":1050},{"style":225},[1051],{"type":29,"value":896},{"type":23,"tag":208,"props":1053,"children":1054},{"style":237},[1055],{"type":29,"value":269},{"type":23,"tag":208,"props":1057,"children":1058},{"style":225},[1059],{"type":29,"value":1060},"Square125",{"type":23,"tag":208,"props":1062,"children":1063},{"style":214},[1064],{"type":29,"value":1065}," =>",{"type":23,"tag":208,"props":1067,"children":1068},{"style":262},[1069],{"type":29,"value":915},{"type":23,"tag":208,"props":1071,"children":1072},{"style":237},[1073],{"type":29,"value":269},{"type":23,"tag":208,"props":1075,"children":1076},{"style":231},[1077],{"type":29,"value":924},{"type":23,"tag":208,"props":1079,"children":1080},{"style":237},[1081],{"type":29,"value":297},{"type":23,"tag":208,"props":1083,"children":1084},{"style":262},[1085],{"type":29,"value":933},{"type":23,"tag":208,"props":1087,"children":1088},{"style":237},[1089],{"type":29,"value":543},{"type":23,"tag":208,"props":1091,"children":1092},{"style":214},[1093],{"type":29,"value":942},{"type":23,"tag":208,"props":1095,"children":1096},{"style":225},[1097],{"type":29,"value":947},{"type":23,"tag":208,"props":1099,"children":1100},{"style":237},[1101],{"type":29,"value":297},{"type":23,"tag":208,"props":1103,"children":1104},{"style":262},[1105],{"type":29,"value":956},{"type":23,"tag":208,"props":1107,"children":1108},{"style":237},[1109],{"type":29,"value":269},{"type":23,"tag":208,"props":1111,"children":1112},{"style":262},[1113],{"type":29,"value":1114},"Point125",{"type":23,"tag":208,"props":1116,"children":1117},{"style":237},[1118],{"type":29,"value":970},{"type":23,"tag":208,"props":1120,"children":1121},{"class":210,"line":337},[1122,1126,1130,1135,1139,1143,1147,1151,1155,1159,1163,1167,1172],{"type":23,"tag":208,"props":1123,"children":1124},{"style":225},[1125],{"type":29,"value":896},{"type":23,"tag":208,"props":1127,"children":1128},{"style":237},[1129],{"type":29,"value":269},{"type":23,"tag":208,"props":1131,"children":1132},{"style":225},[1133],{"type":29,"value":1134},"Triangle",{"type":23,"tag":208,"props":1136,"children":1137},{"style":214},[1138],{"type":29,"value":991},{"type":23,"tag":208,"props":1140,"children":1141},{"style":262},[1142],{"type":29,"value":915},{"type":23,"tag":208,"props":1144,"children":1145},{"style":237},[1146],{"type":29,"value":269},{"type":23,"tag":208,"props":1148,"children":1149},{"style":231},[1150],{"type":29,"value":924},{"type":23,"tag":208,"props":1152,"children":1153},{"style":237},[1154],{"type":29,"value":297},{"type":23,"tag":208,"props":1156,"children":1157},{"style":262},[1158],{"type":29,"value":933},{"type":23,"tag":208,"props":1160,"children":1161},{"style":237},[1162],{"type":29,"value":543},{"type":23,"tag":208,"props":1164,"children":1165},{"style":214},[1166],{"type":29,"value":942},{"type":23,"tag":208,"props":1168,"children":1169},{"style":225},[1170],{"type":29,"value":1171}," TriangleWave",{"type":23,"tag":208,"props":1173,"children":1174},{"style":237},[1175],{"type":29,"value":1176},"()),\n",{"type":23,"tag":208,"props":1178,"children":1179},{"class":210,"line":364},[1180,1184,1188,1193,1197,1201,1205,1209,1213,1217,1221,1225,1230],{"type":23,"tag":208,"props":1181,"children":1182},{"style":225},[1183],{"type":29,"value":896},{"type":23,"tag":208,"props":1185,"children":1186},{"style":237},[1187],{"type":29,"value":269},{"type":23,"tag":208,"props":1189,"children":1190},{"style":225},[1191],{"type":29,"value":1192},"PseudoTriangle",{"type":23,"tag":208,"props":1194,"children":1195},{"style":214},[1196],{"type":29,"value":1065},{"type":23,"tag":208,"props":1198,"children":1199},{"style":262},[1200],{"type":29,"value":915},{"type":23,"tag":208,"props":1202,"children":1203},{"style":237},[1204],{"type":29,"value":269},{"type":23,"tag":208,"props":1206,"children":1207},{"style":231},[1208],{"type":29,"value":924},{"type":23,"tag":208,"props":1210,"children":1211},{"style":237},[1212],{"type":29,"value":297},{"type":23,"tag":208,"props":1214,"children":1215},{"style":262},[1216],{"type":29,"value":933},{"type":23,"tag":208,"props":1218,"children":1219},{"style":237},[1220],{"type":29,"value":543},{"type":23,"tag":208,"props":1222,"children":1223},{"style":214},[1224],{"type":29,"value":942},{"type":23,"tag":208,"props":1226,"children":1227},{"style":225},[1228],{"type":29,"value":1229}," PseudoTriangleWave",{"type":23,"tag":208,"props":1231,"children":1232},{"style":237},[1233],{"type":29,"value":1176},{"type":23,"tag":208,"props":1235,"children":1236},{"class":210,"line":659},[1237,1241,1245,1250,1255,1259,1263,1267,1271,1275,1279,1283,1288],{"type":23,"tag":208,"props":1238,"children":1239},{"style":225},[1240],{"type":29,"value":896},{"type":23,"tag":208,"props":1242,"children":1243},{"style":237},[1244],{"type":29,"value":269},{"type":23,"tag":208,"props":1246,"children":1247},{"style":225},[1248],{"type":29,"value":1249},"LowbitNoise",{"type":23,"tag":208,"props":1251,"children":1252},{"style":214},[1253],{"type":29,"value":1254},"    =>",{"type":23,"tag":208,"props":1256,"children":1257},{"style":262},[1258],{"type":29,"value":915},{"type":23,"tag":208,"props":1260,"children":1261},{"style":237},[1262],{"type":29,"value":269},{"type":23,"tag":208,"props":1264,"children":1265},{"style":231},[1266],{"type":29,"value":924},{"type":23,"tag":208,"props":1268,"children":1269},{"style":237},[1270],{"type":29,"value":297},{"type":23,"tag":208,"props":1272,"children":1273},{"style":262},[1274],{"type":29,"value":933},{"type":23,"tag":208,"props":1276,"children":1277},{"style":237},[1278],{"type":29,"value":543},{"type":23,"tag":208,"props":1280,"children":1281},{"style":214},[1282],{"type":29,"value":942},{"type":23,"tag":208,"props":1284,"children":1285},{"style":225},[1286],{"type":29,"value":1287}," LowBitNoiseWave",{"type":23,"tag":208,"props":1289,"children":1290},{"style":237},[1291],{"type":29,"value":1176},{"type":23,"tag":208,"props":1293,"children":1294},{"class":210,"line":694},[1295,1301,1305,1309,1313,1317,1321,1325,1329,1333,1337,1341,1345,1349,1353],{"type":23,"tag":208,"props":1296,"children":1298},{"style":1297},"--shiki-default:#C99076",[1299],{"type":29,"value":1300},"    _",{"type":23,"tag":208,"props":1302,"children":1303},{"style":214},[1304],{"type":29,"value":1065},{"type":23,"tag":208,"props":1306,"children":1307},{"style":262},[1308],{"type":29,"value":915},{"type":23,"tag":208,"props":1310,"children":1311},{"style":237},[1312],{"type":29,"value":269},{"type":23,"tag":208,"props":1314,"children":1315},{"style":231},[1316],{"type":29,"value":924},{"type":23,"tag":208,"props":1318,"children":1319},{"style":237},[1320],{"type":29,"value":297},{"type":23,"tag":208,"props":1322,"children":1323},{"style":262},[1324],{"type":29,"value":933},{"type":23,"tag":208,"props":1326,"children":1327},{"style":237},[1328],{"type":29,"value":543},{"type":23,"tag":208,"props":1330,"children":1331},{"style":214},[1332],{"type":29,"value":942},{"type":23,"tag":208,"props":1334,"children":1335},{"style":225},[1336],{"type":29,"value":947},{"type":23,"tag":208,"props":1338,"children":1339},{"style":237},[1340],{"type":29,"value":297},{"type":23,"tag":208,"props":1342,"children":1343},{"style":262},[1344],{"type":29,"value":956},{"type":23,"tag":208,"props":1346,"children":1347},{"style":237},[1348],{"type":29,"value":269},{"type":23,"tag":208,"props":1350,"children":1351},{"style":262},[1352],{"type":29,"value":965},{"type":23,"tag":208,"props":1354,"children":1355},{"style":237},[1356],{"type":29,"value":1357},"))\n",{"type":23,"tag":208,"props":1359,"children":1360},{"class":210,"line":733},[1361],{"type":23,"tag":208,"props":1362,"children":1363},{"style":237},[1364],{"type":29,"value":1365},"};\n",{"type":23,"tag":31,"props":1367,"children":1368},{},[1369],{"type":29,"value":1370},"矩形波はデューティ比（パルス幅の比率）違いで3種類入れています。50% / 25% / 12.5%でそれぞれ倍音構成が変わるので、音色の違いが聞いてわかるはずです。",{"type":23,"tag":24,"props":1372,"children":1374},{"id":1373},"バッファリング生成wasmを固めないために",[1375],{"type":29,"value":1376},"バッファリング生成：WASMを固めないために",{"type":23,"tag":31,"props":1378,"children":1379},{},[1380],{"type":29,"value":1381},"PICOMの初期実装ではまず曲全体のPCMを一気に生成していたのですが、長い曲だとBlazor WebAssemblyのUIスレッドが秒単位で固まる現象に遭遇しました。",{"type":23,"tag":31,"props":1383,"children":1384},{},[1385,1387,1392,1394,1399,1401,1407],{"type":29,"value":1386},"解決策は",{"type":23,"tag":388,"props":1388,"children":1389},{},[1390],{"type":29,"value":1391},"バッファリング生成",{"type":29,"value":1393},"です。SoundMakerの",{"type":23,"tag":46,"props":1395,"children":1397},{"className":1396},[],[1398],{"type":29,"value":173},{"type":29,"value":1400},"は、指定サンプル数（PICOMでは44100、つまり1秒分）ごとにPCMチャンクをyieldする",{"type":23,"tag":46,"props":1402,"children":1404},{"className":1403},[],[1405],{"type":29,"value":1406},"IEnumerable",{"type":29,"value":1408},"を返します。これを1個ずつ処理して連結することで、進捗表示も自然に実装できました。",{"type":23,"tag":31,"props":1410,"children":1411},{},[1412],{"type":29,"value":1413},"ちなみに、この方法だけでは完全に画面側のフリーズを解消することはできなかったため、マルチスレッド化を行うことで完全に解消できました。",{"type":23,"tag":1415,"props":1416,"children":1419},"link-card",{"label":1417,"to":1418},"Blazor WASMマルチスレッド化の詳細はこちら⬇️","/articles/tech/blazor/blazor-wasm-multithreading",[],{"type":23,"tag":198,"props":1421,"children":1423},{"className":200,"code":1422,"language":202,"meta":7,"style":7},"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",[1424],{"type":23,"tag":46,"props":1425,"children":1426},{"__ignoreMap":7},[1427,1477,1517,1542,1566,1573,1606,1659,1714,1804,1811,1844,1852,1903,1934,1965,1983,2037],{"type":23,"tag":208,"props":1428,"children":1429},{"class":210,"line":17},[1430,1434,1439,1443,1447,1451,1455,1459,1464,1468,1473],{"type":23,"tag":208,"props":1431,"children":1432},{"style":214},[1433],{"type":29,"value":418},{"type":23,"tag":208,"props":1435,"children":1436},{"style":231},[1437],{"type":29,"value":1438}," buffers",{"type":23,"tag":208,"props":1440,"children":1441},{"style":237},[1442],{"type":29,"value":428},{"type":23,"tag":208,"props":1444,"children":1445},{"style":262},[1446],{"type":29,"value":915},{"type":23,"tag":208,"props":1448,"children":1449},{"style":237},[1450],{"type":29,"value":269},{"type":23,"tag":208,"props":1452,"children":1453},{"style":231},[1454],{"type":29,"value":173},{"type":23,"tag":208,"props":1456,"children":1457},{"style":237},[1458],{"type":29,"value":297},{"type":23,"tag":208,"props":1460,"children":1461},{"style":262},[1462],{"type":29,"value":1463},"startIndex",{"type":23,"tag":208,"props":1465,"children":1466},{"style":237},[1467],{"type":29,"value":543},{"type":23,"tag":208,"props":1469,"children":1470},{"style":300},[1471],{"type":29,"value":1472}," 44100",{"type":23,"tag":208,"props":1474,"children":1475},{"style":237},[1476],{"type":29,"value":361},{"type":23,"tag":208,"props":1478,"children":1479},{"class":210,"line":243},[1480,1484,1489,1493,1497,1502,1507,1512],{"type":23,"tag":208,"props":1481,"children":1482},{"style":214},[1483],{"type":29,"value":418},{"type":23,"tag":208,"props":1485,"children":1486},{"style":231},[1487],{"type":29,"value":1488}," pcmChunks",{"type":23,"tag":208,"props":1490,"children":1491},{"style":237},[1492],{"type":29,"value":428},{"type":23,"tag":208,"props":1494,"children":1495},{"style":214},[1496],{"type":29,"value":942},{"type":23,"tag":208,"props":1498,"children":1499},{"style":225},[1500],{"type":29,"value":1501}," List",{"type":23,"tag":208,"props":1503,"children":1504},{"style":237},[1505],{"type":29,"value":1506},"\u003C",{"type":23,"tag":208,"props":1508,"children":1509},{"style":256},[1510],{"type":29,"value":1511},"byte",{"type":23,"tag":208,"props":1513,"children":1514},{"style":237},[1515],{"type":29,"value":1516},"[]>();\n",{"type":23,"tag":208,"props":1518,"children":1519},{"class":210,"line":252},[1520,1524,1529,1533,1538],{"type":23,"tag":208,"props":1521,"children":1522},{"style":256},[1523],{"type":29,"value":438},{"type":23,"tag":208,"props":1525,"children":1526},{"style":231},[1527],{"type":29,"value":1528}," totalPcmSize",{"type":23,"tag":208,"props":1530,"children":1531},{"style":237},[1532],{"type":29,"value":428},{"type":23,"tag":208,"props":1534,"children":1535},{"style":300},[1536],{"type":29,"value":1537}," 0",{"type":23,"tag":208,"props":1539,"children":1540},{"style":237},[1541],{"type":29,"value":691},{"type":23,"tag":208,"props":1543,"children":1544},{"class":210,"line":281},[1545,1549,1554,1558,1562],{"type":23,"tag":208,"props":1546,"children":1547},{"style":256},[1548],{"type":29,"value":438},{"type":23,"tag":208,"props":1550,"children":1551},{"style":231},[1552],{"type":29,"value":1553}," chunkIndex",{"type":23,"tag":208,"props":1555,"children":1556},{"style":237},[1557],{"type":29,"value":428},{"type":23,"tag":208,"props":1559,"children":1560},{"style":300},[1561],{"type":29,"value":1537},{"type":23,"tag":208,"props":1563,"children":1564},{"style":237},[1565],{"type":29,"value":691},{"type":23,"tag":208,"props":1567,"children":1568},{"class":210,"line":311},[1569],{"type":23,"tag":208,"props":1570,"children":1571},{"emptyLinePlaceholder":510},[1572],{"type":29,"value":513},{"type":23,"tag":208,"props":1574,"children":1575},{"class":210,"line":337},[1576,1580,1585,1589,1593,1597,1602],{"type":23,"tag":208,"props":1577,"children":1578},{"style":214},[1579],{"type":29,"value":418},{"type":23,"tag":208,"props":1581,"children":1582},{"style":231},[1583],{"type":29,"value":1584}," totalSamples",{"type":23,"tag":208,"props":1586,"children":1587},{"style":237},[1588],{"type":29,"value":428},{"type":23,"tag":208,"props":1590,"children":1591},{"style":262},[1592],{"type":29,"value":915},{"type":23,"tag":208,"props":1594,"children":1595},{"style":237},[1596],{"type":29,"value":269},{"type":23,"tag":208,"props":1598,"children":1599},{"style":231},[1600],{"type":29,"value":1601},"GetAllTracks",{"type":23,"tag":208,"props":1603,"children":1604},{"style":237},[1605],{"type":29,"value":240},{"type":23,"tag":208,"props":1607,"children":1608},{"class":210,"line":364},[1609,1614,1619,1623,1628,1632,1637,1641,1646,1651,1655],{"type":23,"tag":208,"props":1610,"children":1611},{"style":237},[1612],{"type":29,"value":1613},"    .",{"type":23,"tag":208,"props":1615,"children":1616},{"style":231},[1617],{"type":29,"value":1618},"Where",{"type":23,"tag":208,"props":1620,"children":1621},{"style":237},[1622],{"type":29,"value":297},{"type":23,"tag":208,"props":1624,"children":1625},{"style":231},[1626],{"type":29,"value":1627},"t",{"type":23,"tag":208,"props":1629,"children":1630},{"style":214},[1631],{"type":29,"value":1065},{"type":23,"tag":208,"props":1633,"children":1634},{"style":262},[1635],{"type":29,"value":1636}," t",{"type":23,"tag":208,"props":1638,"children":1639},{"style":237},[1640],{"type":29,"value":269},{"type":23,"tag":208,"props":1642,"children":1643},{"style":262},[1644],{"type":29,"value":1645},"Count",{"type":23,"tag":208,"props":1647,"children":1648},{"style":214},[1649],{"type":29,"value":1650}," !=",{"type":23,"tag":208,"props":1652,"children":1653},{"style":300},[1654],{"type":29,"value":1537},{"type":23,"tag":208,"props":1656,"children":1657},{"style":237},[1658],{"type":29,"value":308},{"type":23,"tag":208,"props":1660,"children":1661},{"class":210,"line":659},[1662,1666,1671,1675,1679,1683,1687,1691,1696,1700,1705,1710],{"type":23,"tag":208,"props":1663,"children":1664},{"style":237},[1665],{"type":29,"value":1613},{"type":23,"tag":208,"props":1667,"children":1668},{"style":231},[1669],{"type":29,"value":1670},"Max",{"type":23,"tag":208,"props":1672,"children":1673},{"style":237},[1674],{"type":29,"value":297},{"type":23,"tag":208,"props":1676,"children":1677},{"style":231},[1678],{"type":29,"value":1627},{"type":23,"tag":208,"props":1680,"children":1681},{"style":214},[1682],{"type":29,"value":1065},{"type":23,"tag":208,"props":1684,"children":1685},{"style":262},[1686],{"type":29,"value":1636},{"type":23,"tag":208,"props":1688,"children":1689},{"style":237},[1690],{"type":29,"value":269},{"type":23,"tag":208,"props":1692,"children":1693},{"style":262},[1694],{"type":29,"value":1695},"EndIndex",{"type":23,"tag":208,"props":1697,"children":1698},{"style":237},[1699],{"type":29,"value":452},{"type":23,"tag":208,"props":1701,"children":1702},{"style":214},[1703],{"type":29,"value":1704}," -",{"type":23,"tag":208,"props":1706,"children":1707},{"style":262},[1708],{"type":29,"value":1709}," startIndex",{"type":23,"tag":208,"props":1711,"children":1712},{"style":237},[1713],{"type":29,"value":691},{"type":23,"tag":208,"props":1715,"children":1716},{"class":210,"line":694},[1717,1721,1726,1730,1735,1739,1743,1747,1752,1756,1760,1764,1768,1773,1777,1782,1786,1791,1795,1800],{"type":23,"tag":208,"props":1718,"children":1719},{"style":256},[1720],{"type":29,"value":438},{"type":23,"tag":208,"props":1722,"children":1723},{"style":231},[1724],{"type":29,"value":1725}," estimatedChunks",{"type":23,"tag":208,"props":1727,"children":1728},{"style":237},[1729],{"type":29,"value":428},{"type":23,"tag":208,"props":1731,"children":1732},{"style":262},[1733],{"type":29,"value":1734}," Math",{"type":23,"tag":208,"props":1736,"children":1737},{"style":237},[1738],{"type":29,"value":269},{"type":23,"tag":208,"props":1740,"children":1741},{"style":231},[1742],{"type":29,"value":1670},{"type":23,"tag":208,"props":1744,"children":1745},{"style":237},[1746],{"type":29,"value":297},{"type":23,"tag":208,"props":1748,"children":1749},{"style":300},[1750],{"type":29,"value":1751},"1",{"type":23,"tag":208,"props":1753,"children":1754},{"style":237},[1755],{"type":29,"value":543},{"type":23,"tag":208,"props":1757,"children":1758},{"style":237},[1759],{"type":29,"value":433},{"type":23,"tag":208,"props":1761,"children":1762},{"style":256},[1763],{"type":29,"value":438},{"type":23,"tag":208,"props":1765,"children":1766},{"style":237},[1767],{"type":29,"value":452},{"type":23,"tag":208,"props":1769,"children":1770},{"style":262},[1771],{"type":29,"value":1772},"Math",{"type":23,"tag":208,"props":1774,"children":1775},{"style":237},[1776],{"type":29,"value":269},{"type":23,"tag":208,"props":1778,"children":1779},{"style":231},[1780],{"type":29,"value":1781},"Ceiling",{"type":23,"tag":208,"props":1783,"children":1784},{"style":237},[1785],{"type":29,"value":297},{"type":23,"tag":208,"props":1787,"children":1788},{"style":262},[1789],{"type":29,"value":1790},"totalSamples",{"type":23,"tag":208,"props":1792,"children":1793},{"style":214},[1794],{"type":29,"value":481},{"type":23,"tag":208,"props":1796,"children":1797},{"style":300},[1798],{"type":29,"value":1799}," 44100.0",{"type":23,"tag":208,"props":1801,"children":1802},{"style":237},[1803],{"type":29,"value":504},{"type":23,"tag":208,"props":1805,"children":1806},{"class":210,"line":733},[1807],{"type":23,"tag":208,"props":1808,"children":1809},{"emptyLinePlaceholder":510},[1810],{"type":29,"value":513},{"type":23,"tag":208,"props":1812,"children":1814},{"class":210,"line":1813},11,[1815,1819,1823,1827,1832,1836,1840],{"type":23,"tag":208,"props":1816,"children":1817},{"style":256},[1818],{"type":29,"value":521},{"type":23,"tag":208,"props":1820,"children":1821},{"style":237},[1822],{"type":29,"value":433},{"type":23,"tag":208,"props":1824,"children":1825},{"style":214},[1826],{"type":29,"value":418},{"type":23,"tag":208,"props":1828,"children":1829},{"style":231},[1830],{"type":29,"value":1831}," wave",{"type":23,"tag":208,"props":1833,"children":1834},{"style":256},[1835],{"type":29,"value":557},{"type":23,"tag":208,"props":1837,"children":1838},{"style":262},[1839],{"type":29,"value":1438},{"type":23,"tag":208,"props":1841,"children":1842},{"style":237},[1843],{"type":29,"value":308},{"type":23,"tag":208,"props":1845,"children":1847},{"class":210,"line":1846},12,[1848],{"type":23,"tag":208,"props":1849,"children":1850},{"style":237},[1851],{"type":29,"value":249},{"type":23,"tag":208,"props":1853,"children":1855},{"class":210,"line":1854},13,[1856,1860,1865,1869,1873,1877,1882,1886,1890,1894,1899],{"type":23,"tag":208,"props":1857,"children":1858},{"style":214},[1859],{"type":29,"value":591},{"type":23,"tag":208,"props":1861,"children":1862},{"style":231},[1863],{"type":29,"value":1864}," chunk",{"type":23,"tag":208,"props":1866,"children":1867},{"style":237},[1868],{"type":29,"value":428},{"type":23,"tag":208,"props":1870,"children":1871},{"style":262},[1872],{"type":29,"value":1831},{"type":23,"tag":208,"props":1874,"children":1875},{"style":237},[1876],{"type":29,"value":269},{"type":23,"tag":208,"props":1878,"children":1879},{"style":231},[1880],{"type":29,"value":1881},"GetBytes",{"type":23,"tag":208,"props":1883,"children":1884},{"style":237},[1885],{"type":29,"value":297},{"type":23,"tag":208,"props":1887,"children":1888},{"style":262},[1889],{"type":29,"value":457},{"type":23,"tag":208,"props":1891,"children":1892},{"style":237},[1893],{"type":29,"value":269},{"type":23,"tag":208,"props":1895,"children":1896},{"style":262},[1897],{"type":29,"value":1898},"BitRate",{"type":23,"tag":208,"props":1900,"children":1901},{"style":237},[1902],{"type":29,"value":361},{"type":23,"tag":208,"props":1904,"children":1906},{"class":210,"line":1905},14,[1907,1912,1916,1921,1925,1930],{"type":23,"tag":208,"props":1908,"children":1909},{"style":262},[1910],{"type":29,"value":1911},"    pcmChunks",{"type":23,"tag":208,"props":1913,"children":1914},{"style":237},[1915],{"type":29,"value":269},{"type":23,"tag":208,"props":1917,"children":1918},{"style":231},[1919],{"type":29,"value":1920},"Add",{"type":23,"tag":208,"props":1922,"children":1923},{"style":237},[1924],{"type":29,"value":297},{"type":23,"tag":208,"props":1926,"children":1927},{"style":262},[1928],{"type":29,"value":1929},"chunk",{"type":23,"tag":208,"props":1931,"children":1932},{"style":237},[1933],{"type":29,"value":361},{"type":23,"tag":208,"props":1935,"children":1937},{"class":210,"line":1936},15,[1938,1943,1948,1952,1956,1961],{"type":23,"tag":208,"props":1939,"children":1940},{"style":262},[1941],{"type":29,"value":1942},"    totalPcmSize",{"type":23,"tag":208,"props":1944,"children":1945},{"style":214},[1946],{"type":29,"value":1947}," +=",{"type":23,"tag":208,"props":1949,"children":1950},{"style":262},[1951],{"type":29,"value":1864},{"type":23,"tag":208,"props":1953,"children":1954},{"style":237},[1955],{"type":29,"value":269},{"type":23,"tag":208,"props":1957,"children":1958},{"style":262},[1959],{"type":29,"value":1960},"Length",{"type":23,"tag":208,"props":1962,"children":1963},{"style":237},[1964],{"type":29,"value":691},{"type":23,"tag":208,"props":1966,"children":1968},{"class":210,"line":1967},16,[1969,1974,1979],{"type":23,"tag":208,"props":1970,"children":1971},{"style":262},[1972],{"type":29,"value":1973},"    chunkIndex",{"type":23,"tag":208,"props":1975,"children":1976},{"style":214},[1977],{"type":29,"value":1978},"++",{"type":23,"tag":208,"props":1980,"children":1981},{"style":237},[1982],{"type":29,"value":691},{"type":23,"tag":208,"props":1984,"children":1986},{"class":210,"line":1985},17,[1987,1992,1997,2001,2006,2011,2016,2020,2025,2029,2033],{"type":23,"tag":208,"props":1988,"children":1989},{"style":262},[1990],{"type":29,"value":1991},"    progress",{"type":23,"tag":208,"props":1993,"children":1994},{"style":214},[1995],{"type":29,"value":1996},"?",{"type":23,"tag":208,"props":1998,"children":1999},{"style":237},[2000],{"type":29,"value":269},{"type":23,"tag":208,"props":2002,"children":2003},{"style":231},[2004],{"type":29,"value":2005},"Report",{"type":23,"tag":208,"props":2007,"children":2008},{"style":237},[2009],{"type":29,"value":2010},"((",{"type":23,"tag":208,"props":2012,"children":2013},{"style":256},[2014],{"type":29,"value":2015},"double",{"type":23,"tag":208,"props":2017,"children":2018},{"style":237},[2019],{"type":29,"value":452},{"type":23,"tag":208,"props":2021,"children":2022},{"style":262},[2023],{"type":29,"value":2024},"chunkIndex",{"type":23,"tag":208,"props":2026,"children":2027},{"style":214},[2028],{"type":29,"value":481},{"type":23,"tag":208,"props":2030,"children":2031},{"style":262},[2032],{"type":29,"value":1725},{"type":23,"tag":208,"props":2034,"children":2035},{"style":237},[2036],{"type":29,"value":361},{"type":23,"tag":208,"props":2038,"children":2040},{"class":210,"line":2039},18,[2041],{"type":23,"tag":208,"props":2042,"children":2043},{"style":237},[2044],{"type":29,"value":370},{"type":23,"tag":31,"props":2046,"children":2047},{},[2048,2054],{"type":23,"tag":46,"props":2049,"children":2051},{"className":2050},[],[2052],{"type":29,"value":2053},"IProgress\u003Cdouble>",{"type":29,"value":2055},"で外から進捗を受け取れるようにしています。UI側ではこのコールバックでプログレスバーを動かします。",{"type":23,"tag":2057,"props":2058,"children":2060},"tradeoff-box",{"cons-label":2059,"pros-label":1391},"シンプルな一括生成",[2061,2084],{"type":23,"tag":2062,"props":2063,"children":2064},"template",{"v-slot:pros":7},[2065],{"type":23,"tag":2066,"props":2067,"children":2068},"ul",{},[2069,2074,2079],{"type":23,"tag":105,"props":2070,"children":2071},{},[2072],{"type":29,"value":2073},"長い曲でもUIが固まらない",{"type":23,"tag":105,"props":2075,"children":2076},{},[2077],{"type":29,"value":2078},"進捗表示を自然に実装できる",{"type":23,"tag":105,"props":2080,"children":2081},{},[2082],{"type":29,"value":2083},"途中でキャンセル可能（PICOMではまだ未実装だが拡張しやすい）",{"type":23,"tag":2062,"props":2085,"children":2086},{"v-slot:cons":7},[2087],{"type":23,"tag":2066,"props":2088,"children":2089},{},[2090,2095],{"type":23,"tag":105,"props":2091,"children":2092},{},[2093],{"type":29,"value":2094},"コードが若干複雑になる",{"type":23,"tag":105,"props":2096,"children":2097},{},[2098],{"type":29,"value":2099},"チャンク境界での波形接続を考慮する必要がある",{"type":23,"tag":24,"props":2101,"children":2103},{"id":2102},"一番の収穫はsoundmakerのapiを自分でドッグフーディングできたこと",[2104],{"type":29,"value":2105},"一番の収穫はSoundMakerのAPIを自分でドッグフーディングできたこと",{"type":23,"tag":31,"props":2107,"children":2108},{},[2109,2111,2116],{"type":29,"value":2110},"今回の実装で一番の収穫は、自作ライブラリSoundMakerの",{"type":23,"tag":388,"props":2112,"children":2113},{},[2114],{"type":29,"value":2115},"API設計の粗さ",{"type":29,"value":2117},"に気付けたことです。以前の記事にも書きましたが、SoundMakerは最初に楽譜をそのままデータ構造にしてしまっていて、「音を配置する」という自然な操作がやや不自然な手順になっていました。",{"type":23,"tag":1415,"props":2119,"children":2122},{"label":2120,"to":2121},"SoundMakerのドッグフーディング体験の詳細はこちら⬇️","/articles/tech/development/oss-dogfooding",[],{"type":23,"tag":31,"props":2124,"children":2125},{},[2126,2128,2134],{"type":29,"value":2127},"PICOMで実際に使ってみると、たとえば音量設定を各コンポーネント（Note / Tie / Tuplet）に対して個別に行う必要があって、上の",{"type":23,"tag":46,"props":2129,"children":2131},{"className":2130},[],[2132],{"type":29,"value":2133},"SetVolumeTo",{"type":29,"value":2135},"のような再帰メソッドを自分で書く羽目になります。これはSoundMaker側に「PolyphonicTrackの全コンポーネントに一括で音量を設定する」APIがあれば解決する話で、ライブラリ側の改善点として積み残しています。",{"type":23,"tag":24,"props":2137,"children":2139},{"id":2138},"まとめ",[2140],{"type":29,"value":2138},{"type":23,"tag":2066,"props":2142,"children":2143},{},[2144,2149,2154,2159],{"type":23,"tag":105,"props":2145,"children":2146},{},[2147],{"type":29,"value":2148},"サウンド生成パイプラインは「フォーマット構築 → トラック生成 → 音符追加 → バッファPCM生成 → WAVラップ」の5段階",{"type":23,"tag":105,"props":2150,"children":2151},{},[2152],{"type":29,"value":2153},"128分音符位置とサンプル位置の変換は、tempo × samplingFrequencyの式で導出できる",{"type":23,"tag":105,"props":2155,"children":2156},{},[2157],{"type":29,"value":2158},"Blazor WebAssemblyではバッファリング生成でないとUIスレッドが固まる",{"type":23,"tag":105,"props":2160,"children":2161},{},[2162],{"type":29,"value":2163},"自作OSSを自分で使うと、APIの粗さが必ず見えるはず！",{"type":23,"tag":2165,"props":2166,"children":2167},"style",{},[2168],{"type":29,"value":2169},"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":7,"searchDepth":243,"depth":243,"links":2171},[2172,2173,2174,2175,2176,2177,2178,2179],{"id":26,"depth":243,"text":26},{"id":92,"depth":243,"text":92},{"id":188,"depth":243,"text":191},{"id":378,"depth":243,"text":381},{"id":800,"depth":243,"text":803},{"id":1373,"depth":243,"text":1376},{"id":2102,"depth":243,"text":2105},{"id":2138,"depth":243,"text":2138},"markdown","content:articles:tech:blazor:soundmaker-wave-generation.md","content","articles/tech/blazor/soundmaker-wave-generation.md","articles/tech/blazor/soundmaker-wave-generation","md",{"title":2187,"description":2188},"Blazor WebAssemblyでマルチスレッドを有効化してUIフリーズを解消する","Blazor WebAssemblyでWasmEnableThreadsを有効化し、音楽生成をバックグラウンドスレッドにオフロードしてUIフリーズを解消した実例です。COOP/COEPの必要性、Channel\u003CT>によるストリーミング再生、PeriodicTimerへの移行で踏んだ不具合も紹介します。",{"title":2190,"description":2191},"OSSを公開したら使いにくかった話｜ドッグフーディングで改善する","OSSライブラリ「SoundMaker」の開発を通じて、自分で使い倒す「ドッグフーディング」の重要性を痛感しました。利用者視点を理解しているつもりでも、実際に使ってみないと気づけない問題があるという体験談です。",{"image":2193,"title":2194,"description":2195},"https://opengraph.githubassets.com/2fdd4359acbcb38ea360d8a42834458b25b0c1e912258985831b8a45d86c30b3/AutumnSky1010/SoundMaker","GitHub - AutumnSky1010/SoundMaker: You can do The following content with this library.   1. make the sound of chiptune(old game sound)  2. export sound to a file of wave format.","You can do The following content with this library.   1. make the sound of chiptune(old game sound)  2. export sound to a file of wave format. - AutumnSky1010/SoundMaker",1780243950547]