<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://coderemixer.com/atom.xml" rel="self" type="application/atom+xml" /><link href="https://coderemixer.com/" rel="alternate" type="text/html" /><updated>2026-02-14T00:30:33+08:00</updated><id>https://coderemixer.com/atom.xml</id><title type="html">Coderemixer 代码混音师</title><subtitle>一个用来发牢骚的网站，以至于无法对所说的话负责。</subtitle><author><name>CodeRemixer</name></author><entry><title type="html">如何在 Godot 里跑浏览器？</title><link href="https://coderemixer.com/2026/02/14/godot-cef" rel="alternate" type="text/html" title="如何在 Godot 里跑浏览器？" /><published>2026-02-14T00:00:00+08:00</published><updated>2026-02-14T00:00:00+08:00</updated><id>https://coderemixer.com/2026/02/14/godot-cef</id><content type="html" xml:base="https://coderemixer.com/2026/02/14/godot-cef"><![CDATA[<p>最近我们在做 CRPG 游戏项目 <strong>Engram</strong> 的时候遇到了一个很典型的痛点：游戏 UI 开发实在是太折磨人了。</p>

<p>用游戏引擎自带的节点系统去堆一个复杂、响应迅速、有动画、有状态管理的界面，效率低得让人想撞墙。作为对比，网页开发那边有几十年的工具链积累——HTML、CSS、JavaScript，还有 React/Vue 这些现代框架，配合 Chrome DevTools 这种神级调试工具，开发体验简直是降维打击。</p>

<p>当时我就在想：<strong>如果能在 Godot 里直接用网页技术写 UI 会怎样？</strong></p>

<p>市面上现有的方案我们都试了一圈，结果都不太理想：</p>
<ul>
  <li><a href="https://github.com/doceazedo/godot_wry">godot_wry</a>：基于系统原生 WebView，轻量是轻量，但只能覆盖在顶层，没法融入 3D 场景，而且不同系统的浏览器内核行为不一致，调试起来能要人命。</li>
  <li><a href="https://github.com/Lecrapouille/gdcef">gdcef</a>：基于 CEF 的 C++ 集成，也是个老牌项目了，但它是纯软件渲染，帧率很难看，4K 下能稳定 15 帧都不容易。</li>
</ul>

<p>我们在 Engram 的 Demo 版本中使用了 <code class="language-plaintext highlighter-rouge">godot_wry</code>。我们甚至给上游交了不少补丁。上线后大概有 50% 的启动崩溃都是因为它在旧版 Windows 上找不到 Edge WebView2，然后 Linux 支持也有问题，继续研发下去能完全改好的希望很渺茫。</p>

<p>既然没有好用的，那就自己造一个吧。于是就有了 <strong>Godot CEF</strong>。</p>

<iframe src="//player.bilibili.com/player.html?isOutside=true&amp;aid=115971079606447&amp;bvid=BV1BK67BCELg&amp;cid=35659517305&amp;p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true" width="100%" height="400px"></iframe>

<h2 id="什么是-godot-cef">什么是 Godot CEF？</h2>

<p>简单来说，这是一个针对 Godot 4.5+ 的高性能 Chromium 集成。为了内存安全和现代化的开发体验，我选择用 Rust（基于 <code class="language-plaintext highlighter-rouge">godot-rust</code>）来写。</p>

<p>核心思路其实很简单：弄一个叫 <code class="language-plaintext highlighter-rouge">CefTexture</code> 的节点，它负责把 Chromium 渲染出来的画面弄成一个 Godot 的纹理。</p>

<p>这就意味着你可以像对待普通贴图一样对待它：贴在 2D 画布上当 UI，贴在 3D Mesh 上当屏幕，甚至用 Shader 给它做弯曲、故障效果，完全没问题。</p>

<p>而且因为是基于 Chromium，它和你在 Chrome 里看到的网页是一模一样的。你可以启动一个 Vite 开发服务器，一边改 Vue/React 代码，一边在 Godot 里实时看到热重载的效果。GDScript 和 JavaScript 之间还能通过 IPC 互相发消息，延迟控制在一帧以内，这就很舒服了。</p>

<p>听起来很简单，大不了就是多写点 GPU 编程代码，一开始我是这么想的。</p>

<h2 id="多-gpu-的噩梦">多 GPU 的噩梦</h2>

<p>原理听起来简单，但实现起来全是坑。其中最让我很痛苦的是<strong>多 GPU 系统</strong>的问题。</p>

<p>现在的笔记本电脑通常都是「核显 + 独显」的配置。Godot 为了性能，通常会跑在独显上；而 CEF 的子进程如果不加干预，默认会跑在省电的核显上。</p>

<p>问题来了：<strong>跨 GPU 的贴图共享是不存在的</strong>。贴图句柄只在创建它的那块显卡上有效。如果 Godot 和 CEF 跑在不同的显卡上，你拿到的就是一个无效的句柄，结果就是黑屏，或者随机崩溃。</p>

<p>为了解决这个问题，我不得不搞了一套 <strong>GPU 设备锁定（Device Pinning）</strong> 的机制。拿到显卡的厂商 ID 和设备 ID 后，再通过命令行参数传给 CEF 子进程，强行按着它的头让它用同一块显卡。这中间还有无数细节，比如 ID 格式转换（Chromium 要十进制，系统给的是十六进制），稍微错一点就是黑屏。</p>

<p>但是还有更坑的，在一些情况下，CEF 甚至枚举不到合适的设备，这和 NVIDIA Optimus 的行为有关，需要在 helper 进程中暴露一个 NvOptimusEnablement 符号才能选到独显。</p>

<h2 id="vulkan-扩展注入">Vulkan 扩展注入</h2>

<p>解决了显卡选择，下一个问题是 <strong>Vulkan 的扩展支持</strong>。</p>

<p>要在进程间共享 Vulkan 纹理，需要一些特定的扩展，比如 Windows 上的 <code class="language-plaintext highlighter-rouge">VK_KHR_external_memory_win32</code> 或者 Linux 上的 <code class="language-plaintext highlighter-rouge">VK_KHR_external_memory_fd</code>。</p>

<p>然而，Godot 在创建 Vulkan 设备的时候，<strong>只会请求它自己需要的扩展</strong>。GDExtension 目前也没有 API 能让我们请求额外的扩展。</p>

<p>引擎不给 API 怎么办？我用了一套运行时函数钩取（Function Hooking）的方案，直接 Hook 了 <code class="language-plaintext highlighter-rouge">vkCreateDevice</code> 这个底层函数。在 Godot 调用它创建设备之前，把我们需要的扩展列表强行塞进去。</p>

<p>但这终究不是长久之计。为了以后能优雅地删掉这些 Hack 代码，我给 Godot 官方提了个 PR (<a href="https://github.com/godotengine/godot/pull/114940">#114940</a>)，专门加了一个 API，允许 GDExtension 在设备创建阶段请求额外的 Vulkan 扩展。等这个 PR 合并了，我们就能堂堂正正地用官方 API 了。</p>

<p>顺便提一嘴 macOS。本来我也想在 macOS 上用 Hook 的方法，结果发现 Godot 在 macOS 上是把 MoltenVK（Vulkan 转 Metal 的层）<strong>静态链接</strong>进去的。这意味着 <code class="language-plaintext highlighter-rouge">vkCreateDevice</code> 的调用是直接跳转，根本没有符号表可以 Hook。</p>

<p>还好 macOS 至少能跑 metal API。</p>

<h2 id="结语">结语</h2>

<p>Godot CEF 这个项目，最初只是为了解决我做 Engram 时的刚需，但现在我觉得它已经足够成熟，可以拿出来给社区用了。</p>

<p>如果你也受够了在游戏引擎里手搓 UI，或者想在游戏里整点网页的狠活，欢迎来试试。项目是 MIT 开源的，二进制文件也预编译好了，拖进 <code class="language-plaintext highlighter-rouge">addons</code> 就能用。</p>

<ul>
  <li><strong>GitHub</strong>: <a href="https://github.com/dsh0416/godot-cef">dsh0416/godot-cef</a></li>
  <li><strong>Godot Assets Store</strong>: <a href="https://godotengine.org/asset-library/asset/4694">Godot CEF</a></li>
</ul>]]></content><author><name>CodeRemixer</name></author><category term="Godot" /><category term="Rust" /><category term="Chromium" /><category term="CEF" /><category term="图形" /><category term="游戏开发" /><category term="UI" /><summary type="html"><![CDATA[最近我们在做 CRPG 游戏项目 Engram 的时候遇到了一个很典型的痛点：游戏 UI 开发实在是太折磨人了。]]></summary></entry><entry><title type="html">1/x + 1/y = 8/79 的整数解</title><link href="https://coderemixer.com/2025/06/25/diophantine-equation" rel="alternate" type="text/html" title="1/x + 1/y = 8/79 的整数解" /><published>2025-06-25T14:30:00+08:00</published><updated>2025-06-25T14:30:00+08:00</updated><id>https://coderemixer.com/2025/06/25/diophantine-equation</id><content type="html" xml:base="https://coderemixer.com/2025/06/25/diophantine-equation"><![CDATA[<p>刷到个视频，讲的是 $\frac{1}{x} + \frac{1}{y} = \frac{8}{79}$ 的解。当然我觉得题目漏了个条件，是整数解，因为如果不加整数条件的话，该问题的解是无穷多的。</p>

<h2 id="原题解">原题解</h2>

<p>原题解发明了一种令人费解的方法叫做「扩大法」，两眼一瞪，就把 $\frac{8}{79}$ 变成 $\frac{80}{790}$，显然地，$x=10,y=790$ 或者 $x=790,y=10$。</p>

<p>我认为这个解法没有任何数学意义，也不具有任何启发性。因为它并没有给出任何数学上的证明，为什么扩大法可以得到整数解。实际上，这个方法是一个注意力惊人的凑数巧合。</p>

<p>我觉得教这种方式的老师根本不配当老师，除了误导学生之外，对于数学的学习没有任何帮助。数学的学习应该是严谨的，应该是有逻辑的，而不是这种「扩大法」的凑数。</p>

<h2 id="思考">思考</h2>

<p>实际上，这是一个丢番图问题。考虑到对于一般的丢番图方程，没有通用的解法。我们不妨先将方程变形为：</p>

\[\frac{1}{x} + \frac{1}{y} = \frac{p}{q}\]

<p>其中 $p,q$ 是互素的整数。我们可以将方程变形为：</p>

\[\begin{aligned}
\frac{1}{x} + \frac{1}{y} &amp;= \frac{p}{q} \\
pxy - qx - qy &amp;= 0 \\
pxy − qx − qy + \frac{q^2}{p} &amp;= \frac{q^2}{p} \\
(px - q)(py - q) &amp;= \frac{q^2}{p}
\end{aligned}\]

<p>对 $q^2$ 进行因数分解，我们设 $d$ 是 $q^2$ 的一个因数，则有：</p>

\[\begin{aligned}
px - q &amp;= d \\
py - q &amp;= \frac{q^2}{d} \\
x &amp;= \frac{d + q}{p} \\
y &amp;= \frac{\frac{q^2}{d} + q}{p}
\end{aligned}\]

<p>若此时 $x,y$ 都是整数，则我们找到了一组解。</p>

<h2 id="验证">验证</h2>

<p>我们来验证一下这个方法。对于原题 $\frac{1}{x} + \frac{1}{y} = \frac{8}{79}$，我们有 $p=8, q=79$。由于 $79^2 = 6241$，考虑到 $79$ 是素数，它的因数有 $1, 79, 6241$。我们来尝试这些因数。</p>

<p>当 $d=1$ 时：</p>

\[\begin{aligned}
px - q &amp;= 1 \\
py - q &amp;= 6241 \\
x &amp;= \frac{1 + 79}{8} = 10 \\
y &amp;= \frac{6241 + 79}{8} = 790
\end{aligned}\]

<p>当 $d=6241$ 时：</p>

\[\begin{aligned}
px - q &amp;= 6241 \\
py - q &amp;= 1 \\
x &amp;= \frac{6241 + 79}{8} = 790 \\
y &amp;= \frac{1 + 79}{8} = 10
\end{aligned}\]

<p>当 $d=97$ 时：</p>

\[\begin{aligned}
px - q &amp;= 79 \\
py - q &amp;= 79 \\
x &amp;= \frac{79 + 79}{8} = 19.75 \\
y &amp;= \frac{79 + 79}{8} = 19.75
\end{aligned}\]

<p>这时 $x,y$ 都不是整数，所以我们不考虑这个因数。</p>

<p>因此，我们得到了整数解 $(10, 790)$ 和 $(790, 10)$。</p>

<h2 id="总结">总结</h2>

<p>进一步地，我们可以发现 $x,y$ 是整数的充要条件是 $ d \equiv -q \pmod{p}$，同理 $\frac{q^2}{d} \equiv -q \pmod{p}$。另外需要注意的是，$d$ 为负数时，$x,y$ 也可能是整数。不过这不影响上面这题的答案，因此不再展开讨论。</p>]]></content><author><name>CodeRemixer</name></author><category term="数学" /><category term="数论" /><category term="不定方程" /><summary type="html"><![CDATA[刷到个视频，讲的是 $\frac{1}{x} + \frac{1}{y} = \frac{8}{79}$ 的解。当然我觉得题目漏了个条件，是整数解，因为如果不加整数条件的话，该问题的解是无穷多的。]]></summary></entry><entry><title type="html">上海国际电影节大卫林奇通宵场观影感受</title><link href="https://coderemixer.com/2025/06/22/siff-david-lynch" rel="alternate" type="text/html" title="上海国际电影节大卫林奇通宵场观影感受" /><published>2025-06-22T17:03:00+08:00</published><updated>2025-06-22T17:03:00+08:00</updated><id>https://coderemixer.com/2025/06/22/siff-david-lynch</id><content type="html" xml:base="https://coderemixer.com/2025/06/22/siff-david-lynch"><![CDATA[<p>昨天 11 点看了上海国际电影节 6 个小时的大卫林奇通宵场散场电影连映《橡皮头》、《妖夜慌踪》、《穆赫兰道》（ <em>Eraserhead</em> + <em>Lost Highway</em> + <em>Mulholland Drive</em>）。观影完之后，有一位观众（下面简称 A）在电影院发表了对《橡皮头》放映非常大的意见。A 的核心观点是觉得杜比厅放映这部电影画面大量出现全黑和全白、画面闪烁的对比度实在太大；但是 A 把问题怀疑到了杜比放映技术本身上，排除了 4K 修复的问题。但其实这个问题几乎广泛存在于所有数字放映电影上，尤其是数字修复电影上，我们不妨可以来讨论一下这个问题。</p>

<h2 id="亮度和量化">亮度和量化</h2>

<p>放映对亮度是有严格定义的。如果你购买过电视，我们知道通常我们用 nit（尼特）单位来衡量亮度，1 nit = 1 cd/m²。常见的广电标准中，sRGB 色域中的白点一般描述为一个 100nit 亮度的 6500K 色温的点表示画面中最亮的点。</p>

<p>数字放映下的另一个非常重要的概念是量化。一个 8bit 的量化意味着我们从最暗到最亮能区分出 256 种不同的亮度；HDR 内容通常需要 10bit 的量化，这使得我们可以区分出 1024 种不同的亮度。然而人类对亮度的认知并不是线性的。显而易见地，画面中偏暗的内容人更容易分清它们的亮度区别。这意味着我们对这 256/1024 种亮度的分配也是不均匀的，这需要通过应用 Gamma 曲线来呈现出画面的更多细节。</p>

<p>在今天做得比较好的电影院里，针对 DCI-P3 色域下的颜色和亮度都能给出非常严肃和准确的测量和定义。杜比放映设备的量化支持一般也都有 10bit，这其实提供了很好的色彩科学条件，<strong>只要放映的内容本身是按照这样的条件处理的</strong>。然而这根本做不到。</p>

<h2 id="旧胶片的局限">旧胶片的局限</h2>

<p>然而电影归根究底是艺术，不是准确还原。这件事情在胶片时代更是尤其有难度。</p>

<p>在《橡皮头》拍摄的 1970s，最常用的黑白胶片应该是 Kodak Tri-X，和今天 Kodak Vision 系列惊人的宽容度不同，Kodak Tri-X 的宽容度一般认为是大约 9 档曝光，这个宽容度甚至无法塞满今天 SDR 内容，更不用说电影院的拷贝片不一定能保持这个宽容度。因此一些导演可以通过在做拍摄时有时会用渐层或使用 Contrast Filter 来降低对比度，母带处理时再通过胶片拷贝延长曝光时间或者改变冲印方式来二次调整这条曲线。</p>

<p>考虑到这点，以上海影城（SHO）的杜比厅的放映条件而言，很难说是放映机的硬件条件导致了宽容度不足或暗部细节消失。因为今天的放映设备的硬件能力太强了，而且我们对色彩科学的研究真的可以很好保证数字格式在这些做得比较好的厅中播放出非常相近的效果。</p>

<h2 id="色调映射tone-mapping">色调映射（Tone mapping）</h2>

<p>然而 1970s 也没有 100nit 的亮度标准（其实今天也没有，更像是某种约定俗成）。我们至今也无法知道在大卫林奇拍摄电影的时候，它严重希望这些黑白闪烁是以何种亮度进行的。以我朴素的观察来看，《橡皮头》的 4K 修复版本至少没用使用 HDR 色域内的亮度。而增加对比度可以保留更多的细节（这和上面说到的数字放映的量化有关），自然白色就会顶得过亮了。我甚至合理怀疑 1970s 的大多数放映设备甚至很可能是不满足 100nit 亮度的，这件事情应该是和 1970s 电影放映时候的结果很不一致的。</p>

<p>当然在放映时，需要通过色调映射（Tone mapping）来将 SDR 内容映射到实际的放映亮度上，需要根据内容的实际亮度范围和放映设备的能力来进行调整。对于《橡皮头》这样的黑白电影，色调映射可能会导致画面中某些细节在高对比度下消失，尤其是在全黑和全白之间的过渡。查询 <a href="https://professionalsupport.dolby.com/s/article/The-Dolby-Vision-Trim-Controls?language=en_US">The Dolby Vision Trim Controls</a> 的文档，我们发现是提供工具将 SDR 电影正确映射到以 100nit 为标准的放映亮度上，而不会用到 HDR 支持的那些特别按特别亮的点上。所以应该和杜比本身更高的对比或宽容度支持无关。</p>

<h2 id="问题">问题</h2>

<p>我觉得这个锅超过一半还是要扣在 4K 修复这件事情上的，问题不出在 4K，问题其实出在胶片的数字化上。考虑到历史的局限性，在做胶片数字化时，其实很难将过去的放映效果完全正确科学地量化。这反而让色彩处理越正确的厅越吃亏。当然确实如果现在是个小影厅，屏幕小一点，投影仪够烂亮度不达标可能感知上还好一点，但仰赖设备不佳带来的不确定的放映体验毕竟不是做修复和档案的正常思路。</p>

<p>当然这个放映环境其实是有别的问题的。首先是这个荧幕太大了，这使得视场角非常大。人眼超过 90 度的视场角都被荧幕覆盖了，这在 1970s 简直不敢想象。这使得画面黑白闪烁时，人眼瞳孔根本来不及切换，这反而导致很多细节没法看清。进一步地，环境光反射处理地非常不好，反射光通过照亮了舞台、音响、观众的脸上又二次返回回了荧幕上，导致了更多的细节消失。</p>]]></content><author><name>CodeRemixer</name></author><category term="电影" /><category term="大卫林奇" /><category term="色彩科学" /><summary type="html"><![CDATA[昨天 11 点看了上海国际电影节 6 个小时的大卫林奇通宵场散场电影连映《橡皮头》、《妖夜慌踪》、《穆赫兰道》（ Eraserhead + Lost Highway + Mulholland Drive）。观影完之后，有一位观众（下面简称 A）在电影院发表了对《橡皮头》放映非常大的意见。A 的核心观点是觉得杜比厅放映这部电影画面大量出现全黑和全白、画面闪烁的对比度实在太大；但是 A 把问题怀疑到了杜比放映技术本身上，排除了 4K 修复的问题。但其实这个问题几乎广泛存在于所有数字放映电影上，尤其是数字修复电影上，我们不妨可以来讨论一下这个问题。]]></summary></entry><entry><title type="html">当我们吹嘘量子并行计算的时候</title><link href="https://coderemixer.com/2025/02/28/how-quantum-computing-works" rel="alternate" type="text/html" title="当我们吹嘘量子并行计算的时候" /><published>2025-02-28T13:00:00+08:00</published><updated>2025-02-28T13:00:00+08:00</updated><id>https://coderemixer.com/2025/02/28/how-quantum-computing-works</id><content type="html" xml:base="https://coderemixer.com/2025/02/28/how-quantum-computing-works"><![CDATA[<p>昨天晚上和老金聊到微软的 Majorana 1 的时候，我说了一段关于量子计算研究的评论。我觉得很有必要再说一次。</p>

<p>我想起 2016 年加拿大总理特鲁多给记者解释量子计算的视频在互联网上被疯传[1]。特鲁多对量子计算的解释概括来说是基于「量子并行计算」概念的，也就是说传统的比特只能同时表达 0 或 1，随着位数的增加，为了在一个庞大的搜索空间中寻找到答案，我们需要枚举所有的答案；而量子比特（qubit）可以同时表达 0 和 1，因此一组 qubits 可以同时表达所有可能的答案，并行地找到我们想要的答案。</p>

<p>我对「量子并行计算」概念的评价就是「正确的废话」，类似的诠释出现在许多科普读物、维基百科等地方。我认为当特鲁多背诵这段内容的时候，他必然是对量子计算一无所知的。</p>

<p>我们不妨考虑一个问题。如果一组量子比特（qubits）在同时表达 0 和 1，那么我们如何「找到我们『想要的』答案」呢？这个「想要」事实上是相当关键的。因为不管量子比特如何表达，基于玻恩公设（Born rule）[2] [3]，你最终只能通过测量（measurement）得到其中一个结果，这个结果还是 0 或者 1 的，其概率为量子态处于本征态的概率幅绝对值的平方。如果量子是单纯地同时表达了所有可能的答案，那么测量的结果就是在状态空间下的随机数发生器，那不叫量子计算，那个叫<a href="/2019/11/13/quantum-poe">量子算命</a>[4]。</p>

<p>秀儿算法（Shor’s Algorithm）[5]向我们展示了量子计算不是算命的关键差异。其通过引入一部分量子算法，使得其可以更快地寻找到余数循环的周期，从而帮助进行大数的因数分解。其中这个寻找余数周期的方法，被称为量子傅里叶变换（Quantum Fourier Transform, QFT）。这其中最巧妙的部分不是把量子比特看成同时表达 0 和 1 的数字，而是看成一个波函数。通过构造波和波之间巧妙的干涉作用，相干相消后使得我们「想要的答案」在其本征态的概率幅大幅上升，而其它答案大幅下降。基于这样的前提，我们才能通过测量其量子态，得到某个我们想要的答案。</p>

<p>这个算法充分体现了量子计算和传统计算机计算方式的根本不同，它们之间以目前的情况来看，不存在简单的替换关系。把量子计算理解成飞跃式的并行计算机是没有道理的。更何况今天量子计算机在工程上仍有大量东西值得解决。</p>

<p>像 Majorana 1 试图构造凝聚态物理上等效的 Majorana 费米子[6]，从而使得其具有拓扑的 qubit 性质，我个人的感觉是在量子退相干问题上可能会有更好的稳定性。首先我不太懂凝聚态，其次我不太懂拓扑，但从论文上看，好像论文作者也不太确定这是不是 Majorana 费米子。但这其实是量子计算在学界上一个很小的突破，和 PR 稿上的大鸣大放可以说是没有任何关系了。</p>

<p>当然以今天量子计算的进展，还是得编故事的。算了，你总不能指望投资人真的理解啥是什么量子态本征态，算个 sin 函数还带复数的，最后还要扔进什么恐怖积分式子里积半天的，已经超越商科数学只能算加减乘除的界限了。不如就编这种东西吧，不编咋骗人投钱来做科研，不做科研未来人类计算怎么进步。</p>

<p>References:</p>

<ol>
  <li>Reuters. (2016, April 17). Internet abuzz after quantum computing lesson by Canada’s Trudeau. Reuters.</li>
  <li>Claude Cohen-Tannoudji, Bernard Diu, Franck Laloë. Quantum Mechanics Volume 1. Hermann.</li>
  <li>Born, Max (1926). “Zur Quantenmechanik der Stoßvorgänge” [On the quantum mechanics of collisions]. Zeitschrift für Physik. 37 (12): 863–867.</li>
  <li>Ding, D. (2022). dsh0416/quantum-i-ching. https://github.com/dsh0416/quantum-i-ching</li>
  <li>Shor, Peter W., Polynomial-Time Algorithms for Prime Factorization and Discrete Logarithms on a Quantum Computer, SIAM J.Sci.Statist.Comput., 1999, 41 (2): 303–332, doi:10.1137/S0036144598347011.</li>
  <li>Aghaee, M., Alcaraz Ramirez, A., Alam, Z., Ali, R., Andrzejczuk, M., Antipov, A., Astafev, M., Barzegar, A., Bauer, B., Becker, J., Bhaskar, U. K., Bocharov, A., Boddapati, S., Bohn, D., Bommer, J., Bourdet, L., Bousquet, A., Boutin, S., … Zilke, J. (2025). Interferometric single-shot parity measurement in InAs–Al hybrid devices. In Nature (Vol. 638, Issue 8051, pp. 651–655). Springer Science and Business Media LLC. https://doi.org/10.1038/s41586-024-08445-2</li>
</ol>]]></content><author><name>CodeRemixer</name></author><category term="量子计算" /><category term="量子物理" /><category term="物理" /><summary type="html"><![CDATA[昨天晚上和老金聊到微软的 Majorana 1 的时候，我说了一段关于量子计算研究的评论。我觉得很有必要再说一次。]]></summary></entry><entry><title type="html">写在甲辰年末</title><link href="https://coderemixer.com/2025/01/28/thoughts-about-deepseek" rel="alternate" type="text/html" title="写在甲辰年末" /><published>2025-01-28T23:00:00+08:00</published><updated>2025-01-28T23:00:00+08:00</updated><id>https://coderemixer.com/2025/01/28/thoughts-about-deepseek</id><content type="html" xml:base="https://coderemixer.com/2025/01/28/thoughts-about-deepseek"><![CDATA[<p>今天有朋友问我：「你居然没点评 DeepSeek，不习惯。」
好吧，看来大家还是喜欢看我大放厥词。</p>

<p>Deepseek R1 的论文<a href="https://doi.org/10.48550/ARXIV.2501.12948">[1]</a>我上周就已经看了，当时是很震撼的。我看到那个 rule-based RL 的时候，真得有种 Aha 的感觉。一些人可能知道，受一些论文<a href="https://doi.org/10.48550/ARXIV.1412.7449">[2]</a><a href="https://doi.org/10.48550/ARXIV.2305.19234">[3]</a>的启发，我们在研发的系统广泛受益于 grammar-constrained sampling 下对 LLM 的调用的效果。但我也从来没有意识到过可以通过如此精巧得构造使得一个自监督的 RL 可以产生如此精妙的效果。但今天我想跳出纯学术角度来讨论一些问题。</p>

<p>我想我们应当确立一个基本的共识。Scaling Law<a href="https://doi.org/10.48550/ARXIV.2001.08361">[4]</a> 和 Moore’s Law 一样，是一种结果而不是原因。科技的进步从来没有银色子弹。没有人会相信 Moore’s Law 会成立，大家不但知道晶体管密度不可能持续地翻倍下去，也知道计算机性能不可能持续地翻倍下去。但是无论是诸如 FinFET、Gate-All-Around 的半导体器件工艺的进步，或者像是 DUV、EUV、浸润式光刻等制造技术的进步，以及从微架构设计和体系结构出发的芯片设计水平的进步，是一代代人智慧和努力的结果让 Moore’s Law 看起来仍然成立。类似地，AI 水平的进步绝无可能是单纯依赖参数量、算力、数据集的进步。如果有人告诉你今天 AI 发展会逐渐停滞，因为互联网上所有数据集都被 AI 吃光了<a href="https://www.economist.com/schools-brief/2024/07/23/ai-firms-will-soon-exhaust-most-of-the-internets-data">[5]</a>，你不觉得可疑吗？如果一个人类有能力理解互联网上那么多数据，它会像这些 AI 一样笨吗？真的不是显而易见地，我们的方法出了什么问题吗？</p>

<p>当然资本市场自有它自己的看法。虽然我不止一次公开说过，在我心目中 Sam Altman 就是个傻◯，但我不否认 Sam 是一位优秀的 CEO，资本市场对他的吹捧是他应得的。我认为问题的本质是政治问题。只要人类社会存在一天，就会有一天主义。归根究底是我们就无法避免对有限资源争夺的分配问题。Sam Altman 把 AI 的能力和参数量、数据集、算力关联起来，试图证明其先发优势是不可撼动的，来试图获得更多的资本或资源，以在竞争中获得优势；对应地 NVIDIA 也欣然接受这套理论，因为这意味着资本市场会相信 NVIDIA 能源源不断地卖出更多的卡。在这条路上走得最远的是我听过最离谱的故事是把 AI 能力和电力和能源关联起来。这些理论细想之下根本不值得推敲，无论是我们从定域性原理<a href="https://doi.org/10.1103/revmodphys.92.021002">[6]</a>的角度还是兰道尔原理<a href="https://doi.org/10.1016/s1355-2198(03)00039-x">[7]</a>的角度，我们都应该很直觉地意识到，这些关联和人类已知的其它智能系统相比，都有至少 6 个以上数量级的差距，今天我们所遇到人工智能的发展瓶颈，绝无可能是这些限制，而是方法的限制。但精通政治的人类会发现一条捷径，只需要将筹码都握在自己手中，就能避免其他人找到正确的路径。那么无论如何下一个里程碑永远是自己的。甚至这种捷径可能是对行业整体更有利的，因为这让钱流向了 AI 行业而不是其它行业。</p>

<p>捷径所省下的工作，最终都是要还债的。CEO 所需要欺骗的对象，是一群最多只能记住 Y=C+I+G+(X-M) 的人，想必这个捷径也很有它的走法。于是 OpenAI 变成了 ClosedAI。就像很多人考学到顶尖学府的顶尖专业，其实对那个专业的研究没有丝毫热情，只是想要毕业后顶着学历去金融公司赚份快钱，而他真正挡住的，是有机会在这个领域研究上发光发热的人，间接地破坏的是人类的利益。但我总是相信，人类历史上所有这些围绕封闭而构成垄断的虫豸未曾成功过，不单单是这样的谎言总是会被戳破，人们终会意识到「以前的蔷薇的梦原来都是虚幻，现在所见的或者才是真的人生」；更重要的是，人类自会找到问题的出路，任何形式的封闭、保密或者封锁都是不可能在长期的尺度上成立的。</p>

<p>最后，我想要借这个机会，讲一讲我家小猫名字的故事。我家小猫的名字叫伽罗瓦，以纪念我最喜欢的法国数学家之一，埃瓦里斯特·伽罗瓦（Évariste Galois）。没有可信的历史告诉我们伽罗瓦是如何去世的，虽然最浪漫的说法是他与人决斗而死。他对代数的前卫研究一直等到伽罗瓦去世 8 年后刘维尔（Joseph Liouville）整理伽罗瓦的遗稿时才被重视[8]。但是这并不影响伽罗瓦帮助人们终于解开了对数学研究工具的桎梏，开创了代数研究的重要新学科。</p>

<p>我想留给大家的话是 Stay Humble，坚持面对问题，然后像西西弗斯一样去解决它，除此之外，我们似乎也别无他法。</p>

<p>写在甲辰年末。</p>

<p>References:</p>

<ol>
  <li>DeepSeek-AI, Guo, D., Yang, D., Zhang, H., Song, J., Zhang, R., Xu, R., Zhu, Q., Ma, S., Wang, P., Bi, X., Zhang, X., Yu, X., Wu, Y., Wu, Z. F., Gou, Z., Shao, Z., Li, Z., Gao, Z., … Zhang, Z. (2025). DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning (Version 1). arXiv. https://doi.org/10.48550/ARXIV.2501.12948</li>
  <li>Vinyals, O., Kaiser, L., Koo, T., Petrov, S., Sutskever, I., &amp; Hinton, G. (2014). Grammar as a Foreign Language (Version 3). arXiv. https://doi.org/10.48550/ARXIV.1412.7449</li>
  <li>Wang, B., Wang, Z., Wang, X., Cao, Y., Saurous, R. A., &amp; Kim, Y. (2023). Grammar Prompting for Domain-Specific Language Generation with Large Language Models (Version 3). arXiv. https://doi.org/10.48550/ARXIV.2305.19234</li>
  <li>Kaplan, J., McCandlish, S., Henighan, T., Brown, T. B., Chess, B., Child, R., Gray, S., Radford, A., Wu, J., &amp; Amodei, D. (2020). Scaling Laws for Neural Language Models (Version 1). arXiv. https://doi.org/10.48550/ARXIV.2001.08361</li>
  <li>The Economist Newspaper. (n.d.). AI firms will soon exhaust most of the internet’s data. The Economist. https://www.economist.com/schools-brief/2024/07/23/ai-firms-will-soon-exhaust-most-of-the-internets-data</li>
  <li>Wharton, K. B., &amp; Argaman, N. (2020). Colloquium : Bell’s theorem and locally mediated reformulations of quantum mechanics. In Reviews of Modern Physics (Vol. 92, Issue 2). American Physical Society (APS). https://doi.org/10.1103/revmodphys.92.021002</li>
  <li>Bennett, C. H. (2003). Notes on Landauer’s principle, reversible computation, and Maxwell’s Demon. In Studies in History and Philosophy of Science Part B: Studies in History and Philosophy of Modern Physics (Vol. 34, Issue 3, pp. 501–510). Elsevier BV. https://doi.org/10.1016/s1355-2198(03)00039-x</li>
  <li>Bruno, L. C., &amp; Baker, L. W. (1999). Math and mathematicians: The history of math discoveries around the world. U X L.</li>
</ol>]]></content><author><name>CodeRemixer</name></author><category term="AI" /><summary type="html"><![CDATA[今天有朋友问我：「你居然没点评 DeepSeek，不习惯。」 好吧，看来大家还是喜欢看我大放厥词。]]></summary></entry><entry><title type="html">sin(x) = 2 的数学对决</title><link href="https://coderemixer.com/2024/11/18/sinx-equals-2" rel="alternate" type="text/html" title="sin(x) = 2 的数学对决" /><published>2024-11-18T01:00:00+08:00</published><updated>2024-11-18T01:00:00+08:00</updated><id>https://coderemixer.com/2024/11/18/sinx-equals-2</id><content type="html" xml:base="https://coderemixer.com/2024/11/18/sinx-equals-2"><![CDATA[<p>这周末我在微信朋友圈搞了个有趣的数学对决。我给 24 小时内所有解出了这个谜题的人都发了 50 元的红包。题目本身其实不算很难，甚至是一道复分析书上的例题。不过试图使用 LLM 来算这道题的人表现出了很神秘的高度一致的错误…</p>

<h2 id="题目">题目</h2>

\[\sin(x) = 2, (x \in \mathbb{C})\]

<h3 id="提示">提示</h3>

<ol>
  <li>欧拉公式</li>
  <li>注意解的数量超过 1 个</li>
  <li>二次方程求根公式</li>
</ol>

<h2 id="题解">题解</h2>

<p>\(sin(x)\) 在实数范围内的值域是 \([-1,1]\)，等于 2 这种事情想都不用想是复数。如果有背到过的话，可能会知道  \(sin(z)=\frac{1}{2}ie^{-iz}-\frac{1}{2}ie^{iz}\)。但这个式子反正每次我用的时候都背不出来，不过我们可以通过很轻松的方法推出来，并且在这题中不需要把化简都做完反而代入计算的时候更快。</p>

<h3 id="欧拉公式">欧拉公式</h3>

<p>因为欧拉公式 \(e^{i\theta} = cos\theta + isin\theta\)，我们将所有的 \(\theta\) 替换成 \(-\theta\)，我们有 \(e^{-i\theta}=cos(-\theta) + isin(-\theta)\)。这时候我们需要利用一点点的「奇变偶不变，符号看象限」的高中数学三角函数技巧（诱导公式），当然像我连这个都差点忘了，直接画一下函数图看一眼就会了。由诱导公式（函数的奇偶性）得到 \(e^{-i\theta} = cos(\theta) -isin(\theta)\)，将欧拉公式和这个式子相减，我们就可以得到 \(e^{i\theta}-e^{-i\theta}=2isin(\theta)\)，把 \(2i\) 移动一下，就得到了我们想要的关于 \(sin(z)\) 函数的一般式子：</p>

\[sin(z) = \frac{1}{2i}e^{iz} - \frac{1}{2i}e^{-iz}\]

<p>代回原题，我们得到：</p>

\[\begin{aligned}
sin(x) &amp;= 2 \\
\frac{1}{2i}e^{ix} - \frac{1}{2i}e^{-ix} &amp;= 2 \\
e^{ix} - e^{-ix} &amp;= 4i \\
e^{ix} - (e^{ix})^{-1} &amp;= 4i \\
e^{ix} - \frac{1}{e^{ix}} &amp;= 4i
\end{aligned}\]

<h3 id="二次函数">二次函数</h3>

<p>到这里其实我们可以看到一个极其明显的 <strong>二次函数</strong>，我们不妨设 \(m = e^{ix}\)，那么我们有：</p>

\[\begin{aligned}
m - \frac{1}{m} &amp;= 4i \\
m^2-1 &amp;= 4mi \\
m^2 -4im -1 &amp;= 0 \\
m &amp;= \frac{4i \pm \sqrt{(-4i)^2+4}}{2} \\
m &amp;= \frac{4i \pm \sqrt{-12}}{2} \\
m &amp;= \frac{4i \pm 2\sqrt{3}i}{2} \\
m &amp;= 2i \pm \sqrt{3}i \\
m &amp;= (2\pm\sqrt{3})i \\
e^{ix} &amp;= (2\pm\sqrt{3})i
\end{aligned}\]

<h3 id="巧妙的复数运算">巧妙的复数运算</h3>

\[\begin{aligned}
e^{ix} &amp;= (2\pm\sqrt{3})i \\
ln(e^{ix}) &amp;= ln((2\pm\sqrt{3})i) \\
ix &amp;= ln(i)+ln(2 \pm \sqrt{3}))
\end{aligned}\]

<p>这里面现在剩下最难懂的东西就是 \(ln(i)\) 了，偷懒的人输入进 Wolfram Alpha，它告诉你答案是 \(\frac{i\pi}{2}\)。但是很可惜，这不是所有的解，我们还是最好想一下这东西是怎么来的。</p>

<p>我们考虑对于所有的 \(z = re^{i\theta}\)，其中 \(r\) 是复数的模长，那么 \(ln(z) = ln(r) + i\theta\)。很可惜同一个 \(z\) 不止能被一个 \(re^{i\theta}\) 描述，因为实际上 \(z = re^{i(\theta + 2\pi n)}, (n \in \mathbb{Z})\) 都成立，于是我们有 \(ln(z) = ln(r) + i\theta + 2\pi ni\)。代入 \(ln(i) = ln1 + \frac{i\pi}{2} + 2\pi ni=\frac{i\pi}{2} + 2\pi ni, (n \in \mathbb{Z})\)。继续代回原式：</p>

\[\begin{aligned}
ix &amp;= \frac{i\pi}{2} + 2\pi ni + ln(2 \pm \sqrt{3})) \\
x &amp;= \frac{\frac{i\pi}{2} + 2\pi ni + ln(2 \pm \sqrt{3}))}{i} \\
x &amp;= \frac{\pi}{2}+\frac{ln(2\pm\sqrt{3})}{i}+ 2\pi n \\
x &amp;= \frac{\pi}{2}-ln(2\pm\sqrt{3})i+2\pi n, (n\in \mathbb{Z})
\end{aligned}\]

<h2 id="阅卷过程">阅卷过程</h2>

<p>如果你提交的答案是：</p>

\[x = \frac{\pi}{2} \pm ln(2 + \sqrt{3})i + 2\pi n, (n \in \mathbb{Z})\]

<p>这也是对的，这经过了一步非常精妙的化简，并且是大多数复分析教科书中的标准答案。</p>

<p>化简正确性的证明：</p>

\[\begin{aligned}
&amp; -ln(2+\sqrt3) - ln(2-\sqrt3)) \\
&amp;= -(ln(2 + \sqrt{3})+ln(2 - \sqrt{3}))\\
&amp;= -ln((2 + \sqrt{3})(2 - \sqrt{3})) \\
&amp;= -ln(2^2-\sqrt3^2) \\
&amp;= -ln(4-3) \\
&amp;= -ln1 \\
&amp;= 0
\end{aligned}\]

<p>我收到了如下的错误答案：</p>

<ol>
  <li>\(x =  - \frac{\pi}{2}\pm i \ln\left(2 + \sqrt{3}\right) + 2\pi n, (n \in \mathbb{Z})\)。这个据提交者说是 GPT o1-preview 的答案（原版本的 LaTeX 格式甚至都有错误，我手动做了修正）。粗看差点以为是对的，仔细一看和正确答案正好差了一个 \(\pi\)。我试着上 GPT o1-preview 上复现了一下，发现算法和正确答案几乎是一样的，然后从某个时刻开始突然就想要凑教科书版本的那个答案，然后就错了…</li>
  <li>\(x=\frac{\pi}{2} - i\ln(2+\sqrt{3})+ 2\pi n\) 。这个答案提交者没有说是什么 AI 算的，但是我怎么看都不像人算的（原版本的 \(\LaTeX\) 错误数量非常多，比上面那个多了一倍，我手动做了修正），而且还少了一半的解，少的位置也很离谱。</li>
</ol>

<p>在所有声明了是 AI 做的答案中，只有一份是正确的（虽然正确的那份完全没过程，直接背了个答案上来）。我去复现了一下，发现 GPT o1-preview 算法几乎都是对的。但这题教科书版本的答案在最后有一步极其精妙的化简，当然这步不做也不影响正确性。o1-preview 一开始计算过程完全正确，但是从某个时刻开始，就拼了命地想往这个教科书版本的答案凑，然后就凑错了。甚至在凑错前 \(\LaTeX\) 的格式至少还是对的，从凑错开始连 \(\LaTeX\) 的格式都开始胡写了。<del>人工队得分，机器队没得分。</del></p>

<h2 id="偷鸡">偷鸡</h2>

<p>把 \(sin(x) = 2\) 输到 Wolfram Alpha 里，给了两组解：</p>

<ol>
  <li>
\[x = 2\pi n + \pi - sin^{-1}(2), n \in \mathbb{Z}\]
  </li>
  <li>
\[x = 2\pi n + sin^{-1}(2), n \in \mathbb{Z}\]
  </li>
</ol>

<p>看起来是个基于诱导公式的废话，不过如果我们继续再在 Wolfram Alpha 里搜一下 \(arcsin(2)\)，又得到一种 Alternate form: \(\frac{\pi}{2}-iln(2+\sqrt{3})\)。（我这里把 Wolfram Alpha 用的 \(log\) 换成了 \(ln\) 符号）。</p>

<p>然后代入回去得到两组解：</p>

<ol>
  <li>
\[x = 2\pi n +\frac{\pi}{2}+iln(2+\sqrt{3}), n \in \mathbb{Z}\]
  </li>
  <li>
\[x = 2\pi n + \frac{\pi}{2}-iln(2+\sqrt{3}) , n \in \mathbb{Z}\]
  </li>
</ol>

<p>简化一下得到：</p>

\[x = 2\pi n + \frac{\pi}{2} \pm iln(2+\sqrt{3}) , n \in \mathbb{Z}\]

<p>偷完了。<del>传统专家模型得一分，大型语言模型没得分。</del></p>]]></content><author><name>CodeRemixer</name></author><category term="数学" /><category term="数学分析" /><summary type="html"><![CDATA[这周末我在微信朋友圈搞了个有趣的数学对决。我给 24 小时内所有解出了这个谜题的人都发了 50 元的红包。题目本身其实不算很难，甚至是一道复分析书上的例题。不过试图使用 LLM 来算这道题的人表现出了很神秘的高度一致的错误…]]></summary></entry><entry><title type="html">半导体从入门到放弃（中）—— 局域性原理与芯片设计</title><link href="https://coderemixer.com/2024/10/05/semiconductor-02" rel="alternate" type="text/html" title="半导体从入门到放弃（中）—— 局域性原理与芯片设计" /><published>2024-10-05T17:30:00+08:00</published><updated>2024-10-05T17:30:00+08:00</updated><id>https://coderemixer.com/2024/10/05/semiconductor-02</id><content type="html" xml:base="https://coderemixer.com/2024/10/05/semiconductor-02"><![CDATA[<p>半导体芯片是现代电子设备的核心，从电视遥控器到超级计算机，无不依赖于这些微小的硅片。然而，芯片设计和制造是一个复杂的过程，涉及深厚的物理、化学知识和精密的工程技术。本篇文章旨在为读者提供一个全景式的了解，涵盖从半导体材料的基本原理到数字电路设计，以及从芯片体系结构到生产工艺的各个环节。</p>

<p>需要注意的是，本篇文章并不会深入探讨具体的设计方法或实现手段，除非特别举例说明。本文的目标是帮助读者理解半导体行业的核心概念，让你在面对与芯片相关的新闻或话题时，能够辨别出无良媒体的误解或错误。然而，读完这篇文章后，读者可能不会成为半导体行业的专家，甚至连入门都谈不上。但会对芯片设计背后的原理有更清晰的认识。</p>

<h2 id="目录导航">目录导航</h2>

<ul>
  <li><a href="/2024/10/05/semiconductor-01">半导体从入门到放弃（上）—— 半导体和数字电路</a></li>
  <li><a href="/2024/10/05/semiconductor-02">半导体从入门到放弃（中）—— 局域性原理与芯片设计</a></li>
  <li>半导体从入门到放弃（下）—— 量产技术（未完成）</li>
</ul>

<h2 id="局域性原理">局域性原理</h2>

<h3 id="物理学家眼中">物理学家眼中</h3>

<p>在经典电磁场理论中，电场和磁场通过 <strong>麦克斯韦方程组</strong> 描述。电磁相互作用并不是瞬时传播的，而是通过电磁波以 <strong>有限的光速</strong> 进行传播。电磁波的传播速度直接体现了局域性原理：一个物体在某个时刻产生的电磁波只会在光速允许的范围内影响其他物体，超出该范围的物体无法感知到这一相互作用。</p>

<p><strong>坡印亭矢量</strong>（Poynting vector）是电磁场中描述能量传递的向量，它定义了电磁能量在空间中如何随时间传播。其公式为：
\(\mathbf{S} = \mathbf{E} \times \mathbf{H}\)
其中 E 是电场强度，H 是磁场强度。坡印亭矢量的方向代表能量流动的方向，大小表示电磁能量的传输速率（能流密度）。电磁能量流的传播是局域的，即能量传递遵循空间中的场和场之间的相互作用，并且传递 <strong>不会超越光速</strong>。电磁场的这种 <strong>局域性</strong> 反映了能量与作用力无法瞬时传递到无限远处，而是有速度限制的，这也是因果律的体现。</p>

<p>在 <strong>广义相对论</strong> 中，时空中的事件只能影响位于其 <strong>光锥</strong> 内的事件。光锥定义了从某个时刻出发，光以光速传播能够到达的所有点的范围。光锥以外的区域是无法被该事件影响的，体现了相对论中的局域性。</p>

<p>在 <strong>量子场论</strong> 中，电磁场是一个 <strong>规范场</strong>，其量子化后的激发对应于 <strong>光子</strong>。光子是电磁力的传递者，即任何带电粒子之间的相互作用是通过光子交换来实现的。量子场论通过光子与带电粒子的相互作用解释了电磁现象。</p>

<p>根据量子场论的原则，电磁场的量子化意味着电磁相互作用是由光子的场量子在特定的时空点中以<strong>局域的</strong>方式激发、传播和相互作用的。也就是说，电磁场只在它所作用的局部时空区域内起作用，并且光子只能在 <strong>有限的范围内</strong> 进行传播，受 <strong>光速的限制</strong>。</p>

<h3 id="在计算机科学家眼中">在计算机科学家眼中</h3>

<p>根据物理学中的局域性原理，信号和信息的传播速度是有限的，特别是受光速限制。这意味着在计算机系统中，<strong>处理器、内存和存储设备</strong> 之间的通信速度不能超越这些物理限制。如果处理器频繁访问远距离的存储单元（如主存或磁盘），由于信号传输时间长，系统性能将受到严重影响。因此，设计计算机系统时，必须依赖局域性原理将常用数据尽可能存储在靠近处理器的高速缓存中。</p>

<p>计算机的局域性原理主要体现在两个方面：</p>

<ul>
  <li>时间局域性</li>
  <li>空间局域性</li>
</ul>

<p>时间局域性指的是，如果某个数据或指令在某一时间点被访问过，那么它很可能在不久之后再次被访问。这意味着 <strong>最近使用的数据更有可能再次被使用</strong>。在现代计算机架构中，我们可以利用缓存存储最近访问过的数据，以便快速再次访问。</p>

<p>空间局域性指的是，如果某个地址的数据被访问，那么它附近的地址也很有可能在不久后被访问。这种访问模式通常表现为 <strong>相邻的数据或指令被连续访问</strong>。这使得系统可以通过预取机制或批量加载数据来提高性能。</p>

<p><img src="/assets/images/memory-hierarchy.png" alt="内存层级" /></p>

<p><strong>为了让处理器不会被存储设备的性能拖累，我们不能让所有的电路都运行在相同的时钟频率上。</strong> 否则我们必须向最慢的设备妥协。而这是 <strong>物理性质决定的</strong>，不随人的意志所转移。</p>

<p>我们可以看到像规模巨大的超级计算机，虽然吞吐量很大，但是延迟通常也很大，无法满足实时计算的需求。即使超级计算机的设计者，花了非常大的力气，从缓存和网络拓扑角度在优化它的延迟，物理限制依然明显地存在。因此并不存在一个 <strong>银色子弹将算力完全量化</strong>。我们必须深入分析我们所要运行的计算的特点，再根据它去权衡我们如何设计电路，才能达到最高的执行性能或效率。</p>

<h2 id="cpu-入门">CPU 入门</h2>

<h3 id="流水线设计">流水线设计</h3>

<p><img src="/assets/images/pipeline-mips.png" alt="Pipeline MIPS" /></p>

<p><strong>流水线设计</strong> 是一种在处理器中用于提高性能的关键技术，它通过将指令的执行过程分解为多个阶段，并在这些阶段之间并行处理多条指令，从而提高处理器的整体吞吐量。通过这种方式，流水线能够在每个时钟周期内处理多个指令，最大化资源的利用率。我们可以通过 <strong>MIPS 5 阶流水线</strong> 的例子来简单介绍流水线的工作原理，以及它如何提高性能。</p>

<p>MIPS 处理器是一种经典的精简指令集（RISC）架构，它的常见参考实现使用了 5 级的指令流水线来提高性能。MIPS 处理器的 5 个流水线阶段包括：</p>

<ol>
  <li><strong>取指阶段（IF, Instruction Fetch）</strong>：从内存中取出当前要执行的指令，并存入指令寄存器。</li>
  <li><strong>译码阶段（ID, Instruction Decode）</strong>：对取出的指令进行译码，识别指令的操作码，并读取相应的操作数。</li>
  <li><strong>执行阶段（EX, Execute）</strong>：对指令进行具体的算术运算或逻辑运算。如果是访存指令，则计算出存储地址。</li>
  <li><strong>存储访问阶段（MEM, Memory Access）</strong>：如果是读/写内存操作，在此阶段完成数据的加载或存储。</li>
  <li><strong>写回阶段（WB, Write Back）</strong>：将运算结果或从内存中读取的数据写回寄存器。</li>
</ol>

<p>每条指令必须依次经过这五个阶段才能完成执行。</p>

<p>在传统的非流水线处理器中，一条指令必须执行完所有的 5 个步骤，才能执行下一条指令。换句话说，处理器每次只能执行一条指令，这会导致大量的资源浪费。例如，当处理器在内存中读取数据时，运算单元处于闲置状态。</p>

<p>由于每个单元的规模都变小了，其能支持的运行频率也在提升，使用多阶流水线的处理器往往能达到更高的运行频率，从而实现更高的处理器性能。</p>

<h3 id="数据竞争">数据竞争</h3>

<p><img src="/assets/images/intel-10g.jpg" alt="Intel 10GHz" /></p>

<p>然而，流水线并不是越深越好的。当一条指令的执行依赖于前一条指令的结果时，如果两条指令同时处于流水线中，可能会 <strong>导致数据错误</strong>。这被称为<strong>「数据冒险 (Data Hazard)」</strong>。</p>

<p><img src="/assets/images/intel-p4-diag.gif" alt="Intel Pentium 4 Processor Architectural Block Diagram" /></p>

<p>Intel 的 Pentium 4 处理器采用了一个超深流水线设计，称为 NetBurst 微架构，其流水线深度达到了 20 阶以上。这种设计目的是允许处理器以更高的频率运行，从而提升性能。理论上，通过将每个流水线阶段的任务分解为更小的子任务，处理器可以在更短的时钟周期内完成每个步骤，使得时钟频率得以提升。然而，这种过于深的流水线设计带来了 <strong>数据冒险</strong> 问题的严重加剧。</p>

<p>流水线的深度直接影响到数据冒险问题的严重程度。在更深的流水线中，由于每条指令的执行分解得更加细致，每条指令所需的处理时间（即从取指到写回的时长）增加，指令之间的数据依赖性可能会跨越更多的时钟周期。这会导致指令之间的依赖关系更加复杂，从而加剧数据冒险问题的发生。当然处理器可以选择原地暂停（流水线空泡 Bubble）来避免数据冒险引起计算错误，但这不就让我们的流水线效率大幅下降了吗？更何况在现代处理器中，还会引入类似分支预测等技术，数据冒险还需要触发分支预测的清空，会带来更大的性能损失。</p>

<p>为了降低数据冒险问题带来的影响，寄存器重命名技术被提了出来。</p>

<p>寄存器重命名的核心思想是，将程序中虚拟寄存器与物理寄存器分开。在现代处理器中，虽然架构中定义的寄存器数量有限，但物理上处理器通常有 <strong>比架构寄存器更多的物理寄存器</strong>。寄存器重命名通过将虚拟寄存器映射到这些额外的物理寄存器，从而消除虚拟寄存器之间的假依赖关系。</p>

<p>假设有以下指令序列：</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. ADD R1, R2, R3   ; R1 = R2 + R3
2. SUB R1, R4, R5   ; R1 = R4 - R5
3. MUL R6, R1, R7   ; R6 = R1 * R7
</code></pre></div></div>

<ul>
  <li>第 1 条和第 2 条指令都写入寄存器 R1，理论上会导致 <strong>写后写冒险（WAW）</strong>，因为它们都尝试写入相同的虚拟寄存器 R1。</li>
  <li>第 3 条指令依赖于第 2 条指令的 R1 值，可能导致 <strong>写后读冒险（RAW）</strong>。</li>
</ul>

<p>传统上，为了防止这两种问题，我们必须等待前一条指令完全执行完才能恢复流水线执行。如果使用寄存器重命名，处理器可能会为每条指令分配不同的物理寄存器：</p>

<ul>
  <li>第 1 条指令的 R1 被重命名为物理寄存器 P1。</li>
  <li>第 2 条指令的 R1 被重命名为物理寄存器 P2。</li>
  <li>第 3 条指令的 R1 操作数从 P2 读取，而不是从 P1 读取。</li>
</ul>

<h3 id="并行执行">并行执行</h3>

<p><strong>超标量</strong>（Superscalar）和 <strong>SIMD</strong>（Single Instruction, Multiple Data，单指令多数据）是现代处理器中两种关键的并行执行技术，它们通过不同的方式提升处理器的计算性能。</p>

<p><img src="/assets/images/superscalar.png" alt="Superscalar Architecture" /></p>

<p>超标量技术允许处理器在每个时钟周期内同时发射和执行多条指令，而不是每个周期只执行一条指令。由于一些指令例如乘法可能需要多个周期才能执行完成，而这时候如果再来一条加法指令，会认为流水线中 EX 阶段已经满载需要等待才能继续执行。但是如果我们在 EX 阶段引入多个计算单元，可以并行处理多个独立的指令，从而增加处理器的吞吐量。</p>

<p><img src="/assets/images/simd.png" alt="Single instruction, multiple data" /></p>

<p>进一步地，图像处理、视频解码、科学计算中大量计算（比如向量和矩阵），是对一组数据进行相同的加法或乘法运算。SIMD 技术使得处理器能够同时存储多个数据，通过一条 SIMD 指令可以同时对这些向量数据进行并行操作，从而增加处理器的吞吐量。</p>

<p>这些指令的本质其实是利用了局域性原理中的空间局域性，因为我们可以用吞吐量换延迟，先成批成批地将内存中的数据搬运到缓存中，当我们要进行批量运算时，就不需要考虑 DRAM 读取的延迟，从而提升了处理器的性能。</p>

<h2 id="gpu-入门">GPU 入门</h2>

<h3 id="硬件-tl">硬件 T&amp;L</h3>

<p>在图形计算中，一个里程碑式的发明就是硬件 T&amp;L（Transform &amp; Lighting，变换和光照）。</p>

<p>其中变换指的是对几何体的位置、旋转、缩放等操作，将三维坐标转换为二维屏幕坐标，通常是一个矩阵乘加运算。而光照计算是指根据场景中的光源、材质以及视角来计算物体表面的光照强度和颜色，通常是一系列的向量运算。</p>

<p><img src="/assets/images/geforce-256.jpg" alt="GeForce 256" /></p>

<p>在传统的图形渲染管线中，这些计算通常由 CPU 执行，然而这些计算算法非常单一，要处理的坐标数量非常多，并不适合 CPU 的串行执行模式。1999 年 NVIDIA 推出了 GeForce 256，其将硬件 T&amp;L 引入了 GPU 的概念，这使得图形性能得到了飞速的提升。</p>

<p>由于 T&amp;L 算法是固定的，我们只需要堆叠大量的矩阵乘加和向量运算单元，就可以提升其处理的吞吐量。它使得 GPU 从一个仅仅负责绘图的设备，变为一个可以处理复杂几何和光照任务的高效并行计算设备。这为后来的 <strong>可编程着色器</strong> 和 <strong>通用计算</strong> 打下了基础。</p>

<h3 id="可编程着色器">可编程着色器</h3>

<p>然而随着图形技术的演化，大家对 3D 图形的处理愈发复杂。2001年，随着 DirectX 8 和 OpenGL 标准的更新，可编程着色器第一次被引入消费级图形处理器。可编程着色器的出现使得开发者可以通过编写自己的小程序，来控制顶点和像素的渲染方式，从而创建复杂的视觉效果。于是 GPU 变成了一种 <strong>适合批量执行小程序</strong> 的硬件结构。</p>

<p><img src="/assets/images/gpu-microarch.png" alt="GPU Microarch" /></p>

<p>GPU 的架构与 CPU 相比，显著的不同在于其强调并行计算。GPU设计了大量简单的计算单元（流处理器），每个单元负责执行多个线程，从而可以同时处理大量的小任务（如着色器程序）。GPU 的这种并行性使其在大规模数据处理任务中能够保持极高的吞吐量。</p>

<p>同时，GPU 的缓存设计与 CPU 不同，它更注重支持大规模数据的批量传输和并行访问。GPU 处理的数据量巨大，尤其是在图形渲染中，需要处理成千上万个像素、顶点和纹理。因此，GPU 的缓存架构专注于高带宽的批量数据处理，能够最大化数据传输的吞吐量。</p>

<h3 id="gpgpu">GPGPU</h3>

<p>在早期的图形渲染管线中，像素着色器主要用于处理每个像素的颜色、纹理、光照等视觉效果。随着可编程着色器的引入，开发者逐渐意识到，像素着色器不仅可以用于图形渲染，还可以执行其他类型的并行计算任务，尤其是在需要处理大量数据的场景下。例如，洋流、大气仿真作为一种复杂的科学运算，涉及大量的浮点运算和向量场计算，开发者发现这些任务的计算模式与图形渲染中的像素处理非常相似。于是开始尝试通过构造一个简单的大于屏幕尺寸的三角形，并在里面填充像素的形式，来进行这些科学运算的模拟。</p>

<p>这推动了计算着色器（Compute Shader）和 GPGPU 的诞生。这也使得后续基于 GPU 进行神经网络深度学习等任务变得可能。</p>

<h2 id="小结">小结</h2>

<p><img src="/assets/images/systolic-array.png" alt="Systolic Array" /></p>

<p>GPGPU 虽然显著提升了神经网络等并行计算任务的性能，但它并不是执行这些任务的最优手段。为了适应深度学习等领域中对矩阵运算的极高需求，今天的 GPU 架构进行了进一步的优化，尤其是引入了像脉动阵列 (Systolic Array) 这样的硬件结构，以提升对矩阵乘加运算的性能。这种架构的变化代表了 GPU 在满足更广泛任务时的一种体系结构的取舍。甚至我们有进一步舍弃 GPU 架构，完全为了神经网络架构优化的 NPU 或 TPU 芯片。这些微架构的差异极大显示了芯片设计上的差异对性能的巨大影响。在下个章节我们会讨论如何通过芯片的生产工艺对芯片又有哪些巨大的影响。</p>]]></content><author><name>CodeRemixer</name></author><category term="半导体" /><category term="数字电路" /><category term="芯片" /><category term="电路" /><summary type="html"><![CDATA[半导体芯片是现代电子设备的核心，从电视遥控器到超级计算机，无不依赖于这些微小的硅片。然而，芯片设计和制造是一个复杂的过程，涉及深厚的物理、化学知识和精密的工程技术。本篇文章旨在为读者提供一个全景式的了解，涵盖从半导体材料的基本原理到数字电路设计，以及从芯片体系结构到生产工艺的各个环节。]]></summary></entry><entry><title type="html">半导体从入门到放弃（上）—— 半导体和数字电路</title><link href="https://coderemixer.com/2024/10/05/semiconductor-01" rel="alternate" type="text/html" title="半导体从入门到放弃（上）—— 半导体和数字电路" /><published>2024-10-05T15:30:00+08:00</published><updated>2024-10-05T15:30:00+08:00</updated><id>https://coderemixer.com/2024/10/05/semiconductor-01</id><content type="html" xml:base="https://coderemixer.com/2024/10/05/semiconductor-01"><![CDATA[<p>半导体芯片是现代电子设备的核心，从电视遥控器到超级计算机，无不依赖于这些微小的硅片。然而，芯片设计和制造是一个复杂的过程，涉及深厚的物理、化学知识和精密的工程技术。本篇文章旨在为读者提供一个全景式的了解，涵盖从半导体材料的基本原理到数字电路设计，以及从芯片体系结构到生产工艺的各个环节。</p>

<p>需要注意的是，本篇文章并不会深入探讨具体的设计方法或实现手段，除非特别举例说明。本文的目标是帮助读者理解半导体行业的核心概念，让你在面对与芯片相关的新闻或话题时，能够辨别出无良媒体的误解或错误。然而，读完这篇文章后，读者可能不会成为半导体行业的专家，甚至连入门都谈不上。但会对芯片设计背后的原理有更清晰的认识。</p>

<h2 id="目录导航">目录导航</h2>

<ul>
  <li><a href="/2024/10/05/semiconductor-01">半导体从入门到放弃（上）—— 半导体和数字电路</a></li>
  <li><a href="/2024/10/05/semiconductor-02">半导体从入门到放弃（中）—— 局域性原理与芯片设计</a></li>
  <li>半导体从入门到放弃（下）—— 量产技术（未完成）</li>
</ul>

<h2 id="半导体">半导体</h2>

<p>在讨论芯片设计的物理基础时，我们首先要了解半导体材料，尤其是硅（Si）。硅是最常用的半导体材料之一，其电子结构对于理解它的电学性质至关重要。但在理解以硅为基础材料的半导体是什么之前，我们先要对「导电」这件事情本身进行一些基础知识的理解。</p>

<h3 id="能带理论">能带理论</h3>

<p>在单个原子中，电子只能处在特定的能量水平（称为能级），而当许多原子紧密排列形成固体材料时，这些能级会相互作用，形成连续的能量带。</p>

<p>我们可以将能量带想象成一座楼梯，每个台阶代表不同的能量水平。电子处在这些台阶上，它们可以在不同的台阶（能量水平）之间跳跃。当你爬得越高，你的能量就会越大（如同现实中的重力势能），反之则越小。这时候我们需要引入三个概念：</p>

<ul>
  <li>
    <p><strong>价带</strong>：相当于楼梯的底部，是电子「站立」的稳定位置。这里的电子与原子核紧密结合，能量较低，移动自由度较小。电子在价带中往往是处于相对稳定的状态，无法自由移动。</p>
  </li>
  <li><strong>导带</strong>：相当于楼梯的上方，是电子可以自由「行走」的地方。电子一旦到达导带，它们就获得了足够的能量，可以在晶体中自由移动，从而形成电流。</li>
  <li><strong>带隙</strong>：价带和导带之间有一段「空隙」，称为 <strong>带隙</strong>（band gap），相当于楼梯上的一大步。这一步（能量差）决定了电子从价带跃迁到导带的难易程度。材料的导电性主要取决于这个带隙的大小。</li>
</ul>

<h4 id="导体没有带隙的楼梯">导体：没有带隙的楼梯</h4>

<p>在导体中，导带和价带是重叠的，或是非常接近，没有明显的带隙。因此，电子不需要“跨越”任何能量差即可进入导带。例如铜的电子在最外层很容易进入导带，因为它的价带和导带几乎重叠。可以想象在铜中，电子就像是在平坦的地板上行走，不需要额外的能量跃迁到导带，因而铜能够很好地导电。</p>

<h4 id="绝缘体巨大带隙的楼梯">绝缘体：巨大带隙的楼梯</h4>

<p>与导体不同，绝缘体（如玻璃、橡胶）具有非常大的带隙，这意味着电子要从价带跃迁到导带需要非常高的能量。在正常条件下，电子几乎不可能跨越这个巨大的能量差。例如石英的带隙非常大。电子被牢牢困在价带中，几乎无法进入导带。这就好像电子被困在楼梯的底部，无法跃过楼梯的高台阶，导致它无法传导电流。</p>

<h4 id="半导体微妙的中间状态">半导体：微妙的中间状态</h4>

<p>半导体位于导体和绝缘体之间，其带隙较小，因此电子在某些条件下能够跃迁到导带，产生导电现象。硅（Si）作为一种典型的半导体材料，它的价带和导带之间有一个适中的能隙，这个能隙的大小允许我们通过外部手段（如加热或施加电场）让电子越过这个「台阶」进入导带。</p>

<p>硅的带隙约为 1.12 电子伏特（eV），比导体的重叠带大，但比绝缘体的小。通过适当的能量输入（如施加电场或光照），硅中的电子能够从价带跃迁到导带。这使得硅在不同条件下既可以作为绝缘体使用，也可以作为导体使用，这正是半导体材料的独特之处。</p>

<h3 id="掺杂">掺杂</h3>

<p>硅原子在元素周期表中位于第十四族，其最外层有 4 个价电子。这些电子通过 <strong>共价键</strong> 与邻近的其他 4 个硅原子形成强力的化学键。硅的晶体结构被称为 <strong>钻石结构</strong>，是一种立体网状结构，在这个结构中，每个硅原子与周围 4 个硅原子通过共价键相连，没有任何自由电子，形成非常稳定的结构。</p>

<p><img src="/assets/images/silicon-structure.png" alt="钻石结构" /></p>

<p>在纯净的硅晶体中，这些共价键牢牢束缚着硅原子的价电子，使它们无法自由移动，因此在低温或常温下，硅的导电性较弱。但硅可以通过 <strong>掺杂（doping）</strong>，即引入微量的杂质原子，来改变它的电学性质。掺杂可以显著影响硅的 <strong>能带结构</strong>，从而使其导电性能发生变化。</p>

<p>当硅晶体中引入 <strong>五价元素</strong>（如磷、砷）时，这些原子带有 5 个价电子，比硅的 4 个价电子多一个。这个多余的电子并不参与共价键的形成，因此成为一个<strong>自由电子</strong>，可以很容易地跃迁到导带中。由于多了一个自由电子，这样的掺杂后的半导体被称为 <strong>「N 型半导体」</strong>。</p>

<p>当硅晶体中引入 <strong>三价元素</strong>（如硼、铝）时，这些原子带有 3 个价电子，比硅原子少 1 个，因此它们无法与周围的四个硅原子形成完整的共价键，结果在晶体结构中留下一个 <strong>空穴</strong>（hole）。这种掺杂产生了正电荷的载流子，即空穴，因此称为 <strong>「P 型半导体」</strong>。</p>

<h3 id="二极管">二极管</h3>

<p><img src="/assets/images/pn-junction.png" alt="PN Junction" /></p>

<p>二极管由 <strong>P 型半导体</strong> 和 <strong>N 型半导体</strong> 相互结合形成，这个接合区域被称为 <strong>PN 结</strong> 。在 P 型区域，主要载流子是 <strong>空穴</strong>（带正电的缺少电子的状态），而在 N 型区域，主要载流子是 <strong>自由电子</strong>（带负电的电子）。这两种材料结合在一起时，会在它们的交界处形成独特的电荷分布，进而影响二极管的导电行为。</p>

<p>当对二极管施加 <strong>正向</strong> 电压时，自由电子能够从 N 型半导体流动到缺少电子的 P 型半导体上，从而形成回路。 然而当给二极管施加 <strong>反向</strong> 电压时，P 型半导体少掉的电子和 N 型半导体多出来的自由电子极大阻止了自由电子从 P 型半导体向 N 型半导体传播，二极管表现为绝缘体，阻止电流通过。</p>

<h3 id="mosfet">MOSFET</h3>

<p>在理解了二极管的基本原理后，我们需要理解数字电路中非常重要的一种半导体器件 MOS 管（MOSFET）。</p>

<p><img src="/assets/images/mosfet.jpg" alt="MOSFET" /></p>

<p>MOS 管由三个关键部分组成：源极（Source）、漏极（Drain）、栅极（Gate）。</p>

<p>当栅极没有施加电压时，源极和漏极之间 <strong>没有形成导电通道</strong>。这意味着即使在源极和漏极之间施加电压，电流也无法通过。</p>

<p>我们以 NMOS 为例（图左），当在 <strong>栅极施加电压</strong> 时，栅极下方的 P 型衬底中的空穴被正电场排斥，电子被吸引到栅极下方，这使得电子组成了两个 N 型半导体之间的通道，使得源极和漏极之间可以被导通。PMOS（图右）则是相反，它需要通过给栅极施加负电压，排斥 N 型衬底的电子，使得源极和漏极导通。</p>

<p><img src="/assets/images/cmos.png" alt="CMOS" /></p>

<p>CMOS 是芯片设计中最基础的单元，它由一个 NMOS 和一个 PMOS 共同组成。在 CMOS 中，当输入信号为高电平时，NMOS 导通，而 PMOS 关闭；当输入信号为低电平时，PMOS 导通，而 NMOS 关闭。</p>

<p>在单独的 NMOS / PMOS 电路中，电路会有持续的电流流过，导致较高的静态功耗。相比之下，CMOS 电路在稳态时几乎没有静态功耗。在一个典型的 CMOS 电路中，NMOS 和 PMOS 交替工作，只有在状态切换时才会有电容充放电的功耗。这意味着在无切换时，功耗接近为零。这使得 CMOS 成为了现代芯片设计中最基础的单元。</p>

<p>我们会发现由 CMOS 管构成的电路相当于一种 <strong>「电子的开关」</strong>，我们可以通过控制栅极来控制电路是不是导通。但是读到这里的你可能还是非常困惑，为什么我们要使用这么复杂的方式来控制电路的开关？现代芯片是如何使用 CMOS 的组合实现各种复杂的计算、存储以及各种工作的？于是我们需要讨论 <strong>「数字电路」</strong> 这个概念。</p>

<h2 id="数字电路基础">数字电路基础</h2>

<p><strong>数字电路</strong> 的核心思想是将电流的流动（通/断）映射为 <strong>二进制</strong> 系统中的 1 和 0 这两种状态。这种映射让我们可以通过简单的电路组合，处理复杂的数据计算和逻辑操作。</p>

<h3 id="基础门电路">基础门电路</h3>

<p>我们不妨试着用 CMOS 搭建一下最基础的几种门电路：非门、与门和或门。</p>

<p><img src="/assets/images/not-gate-off.png" alt="Not Gate Off" /></p>

<p><img src="/assets/images/not-gate-on.png" alt="Note Gate On" /></p>

<p>在上面这个电路图中，当左侧给定一个高电平的信号时，PMOS 管截止，NMOS 管导通，右侧输出低电平；当左侧给定一个低电平的信号时，PMOS 管导通，NMOS 管截止，右侧输出高电平。从而我们实现了一个将 <strong>信号反转</strong> 的功能，这样的逻辑电路被称为 <strong>「非门（NOT）」</strong>。可以点击<a href="https://falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgpABZsKBTAWjDACgxCURtC8QUKGrWzdBw7gBMGAMwCGAVwA2AFyZKGk8FB0xI7AOYixQ4wISFdbAO480A0737ioNu1UzdRH05DffwDEsAlz9bTwE8ZyjI-jCBDBMJRNjXcIsHZKS0hO5ODxTQtxQY-NzwQip4tGdTMGJsvwBnAXxM80tQ7XklJoY2ACVwBvayrqoaKiQqqGgEf3tcYQCA+ICwEvdwGmE-GVSy9cqeejA52cgUNn2S6P4QmNFwc70rofXNgOJLGcnqGZg8yMK0Wi14ViAA">这个链接</a>在线看一下非门的实现。</p>

<p><img src="/assets/images/and-gate.png" alt="And Gate" /></p>

<p><img src="/assets/images/or-gate.png" alt="Or Gate" /></p>

<p>类似地，我们还可以得到<a href="https://falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgpABZsKBTAWjDACgxCULCqwNCILjXACQ3ACYMAZgEMArgBsALk0UMJ4KNpiR2AcyEoR-QcPH4dbAO6jBKY+F7jHkNgGc7LkQ5+utcoruDGwASk5UvuAoeN46tFRIVMnQCDYguCJ4VJkgxIJu0uIY3GA0IrllFfRgqVCwKGEZkFk5LbSxyQnUXTBp4ZyRJdGxmNxdNInxfR5eY3HzfCCBwemmxaXOi2xF2Ail5Rl4JofYNXW6jba5+c0mMVDpN4LYx3kFT2+3r-edOxn7CyxH5AjKlC4Nf57bjzEGw8FJSG2FAIezDFGCKqPa7YTGnQFYtw4+yOM72SxEjK40HQ0GUsk06mEp7tQZ3Lz01licqxdb0t5snkRbG0Gi85xCvmfEzEbiS2Ui+VysUc6XiVFHPwiNyGOF4YFvbBoKzIjVY+bM5GOKIMqKUqLzDEbEVO2GA7aGBBgGHDL0w-VWcJ+kDZCjejrxSY9HSpf5iyJR4PGHII+qQRpFePgcPB1jcM7gCHp9LB0PB26UpMapPDSlZvO0QgmcN1pvZuVtqW2SXcztiVsmXs+KMD8RRrPJkW5h65-sl8NRWcfWxLnh8OdAA">与门</a>和<a href="https://falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgpABZsKBTAWjDACgxCUKU8QUaVBMW4Cq3ACYMAZgEMArgBsALk0UMJ4KNpiR2Acwoj+go6IzcqkNgHdwGQuBo1whKmGdQ2AZ3uOPLigCTi7uIHKK3gxsAEqu7p5gvCE6tEKpMAix8eDGSXxgxlZp1MWZPn65osGFlloRUbaVtTkt1oY0eFTY2I6d3Qh11nH9KaOsQyVIZdBZ0rSdILguo8tL3GCzULAobPPOBQ60XZU94Fu6u3bjnuNuXtcn2GjH3b0PC4d9z5XW1z9gI50USmP60H5icHcNZg4FLSArRYwprjYyjSGwk68Pjo0EorGmQZ8DF7JZ4FwBWicFJnTbTHak7DkqpU-zGWkXBnXamcKg0HlHazzTCiPB8NbY9bnemQXbzHqOSW9YliqV07aytiGZXrbqEcXvKxNNYmhG6j4ml4655GuzWs3Wl5goIUzwoBD+TzOj005mU72ewI+yUB-iqkVhvjOmrsl5tY1x2PiVVgm2RpbvEPGv1JlnDVkgE78vpR7SEjKzFHUos85Kw6loHENvF2s0RiUt+GBUzFky2pb6-gWAsR+uK4e9kncxU9iF4oA">或门</a>，有了与、或、非门，我们就可以围绕这三者构造大量的计算。我们不妨来看一下，我们如何使用这三个的组合来实现加法运算。</p>

<h3 id="加法器">加法器</h3>

<p><img src="/assets/images/half-adder.png" alt="Half Adder" /></p>

<p>如上图的<a href="https://falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgpABZsKBTAWjDACgAZcQqtPcMChB8o4EADMAhgBsAzg2qQ2YSiFw1uVbAiFgeooVQSdNwhIQFCU50VSlyFSJQHdTIvVTA0NL0143q4N5QbK4eZhYi1ha+UTbuGDGhwvjgiWqQGmDpvtq6+jR6pkoAshT64Tz84bzQxgAe5RBeVIRgxEH0GigaAMKSAE4DAJ7KOhndPWrjKFO81MZh+iKBIrGp0cI0vDax2xFbvPt7R1qZhyEqVDR4vFM6-LM+wgvJhRbh7yn8uTNTeRdflZ9t5Hsc2ABJWhFEQ0QjdVJUGBIRa0eHfWi3DG+UEXG6nEKuB7Ce7LOZsRoIBAdNAWQjYCBoDrdDQAeQArgAXAAOXLeRXC5k8+lK5TuGiqJOeKDqbCAA">电路</a>通过逻辑门的组合，实现了两个一位二进制数的相加，被称为一个<strong>「半加法器」</strong>。可以想象，</p>

<p><img src="/assets/images/full-adder.png" alt="Full Adder" /></p>

<p>如果我们将半加法器串接，就可以处理两个二进制数和一个上一位计算是否要进位的加法器。这样的<a href="https://falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgpABZsKBTAWjDACgAZcQqtPcMChB8o4EADMAhgBsAzg2qQ2YSiFw1uVbAiFgeooUgSdNwhIQFCU50RClyFSJQHdTIvVTA0NL0143q4N5QbK4eZhYi1ha+UTbuGDGhwvjgiWqQGmDpvtq6+jR6pkoqQoEoKAE6wpUG1MZh+iLlqbGp0cI0vDaxXRGdvH29g1qZAyEqVDR4vLU6-BU+wqINtEXhhZGtyXk1VVZDO9WVU9PjSgCSa1v8NIQaIlQwRsl3D6nTvNuu3gt9n+dkvM9hQmrUlABZZB4fiCWFgbAWOEGaDGAAe0PIrE8KmIAnoDw0AHkAK4AFwADuTkmBIDZkbSYeAUPxfGQkcFaal-CFXKRdIJkNzBZcQMROYL+ZZRM8KMkYZ5guygj55TNmfwFSreWLgvddVkRcpVFzYSzkPTzbwVslErDgnhsLpgr47aY3TzXYj3YRYfpfL74UJHQKhCVqiGNSAtcjrVRVlr9eKNPqvRZ1ZH1Wn3d6s8lA7RNU7CzqC6whAWzuGqJH9YmlmG5Vwy4L7jjG1R7PJFGwMToa0hCLho7dOiAOJJZGSADqyADCkgATouAJ5AsEaHCRcG272LEBb4rGoTEfQsJFwfjnuovPl6jTJmXJR9UU9PHWbUyfpjF3zf4tviAP5hjSl5Aes+BXkUSgYoyeJMH0rA0OQP7kISIALsua5QoyUFIgiFjXrwqJsEAA">电路</a>被称为<strong>「全加法器」</strong>。我们只需要堆叠这样的电路就可以实现 <strong>任意位数的整数加法</strong>。</p>

<p><img src="/assets/images/von-neumann-architecture.png" alt="Von Neumann Architecture" /></p>

<p>使用类似的技术，你可以实现各种计算、分支判断、控制等电路。然而这还缺少一个非常关键的部分，对于冯诺依曼架构而言，我们还没有解决<strong>「存储」</strong>这个问题。例如我们想要先算加法，再算乘法之类的，我们总是希望程序能自动执行这些步骤，并且存储中间结果，而不是我们认为每次都重新输入。</p>

<h3 id="锁存器">锁存器</h3>

<p><img src="/assets/images/latch.png" alt="Latch" /></p>

<p>在这个<a href="https://falstad.com/circuit/circuitjs.html?ctz=CQAgjCAMB0l3BWcMBMcUHYMGZIA4UA2ATmIxAUgpABZsKBTAWjDACgxKRs8apa8VISBT8EHLjz5o8AqjP6iq4zvUL4BIDJD41Z86ioRqNYdVp3hzB5WwDu3BKJQo+Uka6hsASo9HFCbjQQAPARZHloJBQo-hhxB2wnEMCk0TMvROSM9wzIez8UwrBiJQK08FLuXkqyhz1hdX1MkCb+Nuw6uRENNoV8hz7e0wxAgYs+Myo2wj5xtrAaPm1dfTYAWQmrKgwaqcUotgAZQpc3SzO4kAAzAEMAGwBnBmp8k-cWVMtPq7unl6Q+QAHtxWCAmDxqrIIUhpHwAMoAFwA9gAnBgAHUe7BB2DBRHIPFkmCocJASLRmMe+SAA">电路</a>中，你可以通过在 <strong>时钟高电平</strong> 时，设置 0 或者 1，使得电路保存下来你的状态。即使之后设置 0 或 1 的信号不在了，也不影响输出。这是我们第一次引入<strong>「时钟」</strong>这个概念，并且只让这个电路在时钟高电平时（也就是时钟上升沿执行后面的逻辑），因为在后面的电路中，我们将 <strong>逻辑门的输出接在了逻辑门的输入</strong> 上，这意味着本质上是<strong>「上个时刻的输出是下个时刻的输入」</strong>，而这个步骤如果执行地过快，可能会由于电路本身的延迟（我们会在之后很具体地讨论这个问题）导致来不及存储，从而导致程序执行的错误。为了避免这个问题，我们引入了时钟。</p>

<p>这种时钟边沿触发机制和时钟速率的管理，是现代芯片设计中实现稳定、可靠、高速运算的关键。如果没有时钟信号的精确控制，复杂电路的同步工作将变得难以保障，而时序错误会导致不可预测的计算结果。</p>

<h3 id="时钟电路">时钟电路</h3>

<h4 id="晶振">晶振</h4>

<p>石英晶体（晶振）是一种压电材料，当对其施加电压时，石英晶体会发生形变，反过来，它也能够在外力作用下产生电压。当晶体振荡器工作时，石英晶体能够在特定的自然频率下发生机械振荡，称为 <strong>谐振</strong>。这意味着当我们将一个噪声信号传给晶振时，其谐振频率附近的频率会被放大和保留，而其它频率会被抑制。在这些噪声源的作用下，晶振可以获得启动所需的初始扰动，进而利用自身的谐振特性生成稳定的时钟信号。</p>

<h4 id="锁相环-pll">锁相环 (PLL)</h4>

<p><img src="/assets/images/pll-circuit.png" alt="PLL 基本原理" /></p>

<p><strong>晶振</strong> 提供的频率通常较低，无法直接满足现代处理器和其他高速电路的需求。因此，设计中常常采用 <strong>PLL 电路</strong>（Phase-Locked Loop，锁相环）对晶振的频率进行放大，从而得到更高的工作频率。</p>

<p>一个典型的PLL电路由以下几个主要模块组成：</p>

<ol>
  <li><strong>相位检测器（Phase Detector，PD）</strong>：检测输入参考信号（例如来自晶振）的相位与PLL反馈信号的相位之间的差异，并输出一个代表相位误差的信号。</li>
  <li><strong>环路滤波器（Loop Filter，LF）</strong>：对相位检测器输出的误差信号进行滤波，去除高频噪声，并将其转化为一个相对平滑的控制电压信号。</li>
  <li><strong>压控振荡器（Voltage-Controlled Oscillator，VCO）</strong>：将滤波器输出的控制电压转换为相应频率的振荡信号。VCO的输出频率随着控制电压的变化而变化。</li>
  <li><strong>分频器（Frequency Divider，FD）</strong>：将VCO输出的高频信号进行分频，生成与输入参考信号相近的低频信号，作为反馈信号输入给相位检测器。</li>
</ol>

<p>PLL 的核心在于<strong>相位检测器</strong>，它对输入的参考信号（晶振提供的频率较低的信号）与<strong>反馈信号</strong>（来自 VCO 输出的信号经过分频器处理后的信号）进行比较，输出一个与相位差成正比的信号。这个相位差会引发控制电压的变化，进而调节 VCO 的输出频率。</p>

<ul>
  <li>如果 VCO 输出的频率过低，控制电压会升高，从而使 VCO 频率增加；</li>
  <li>如果 VCO 输出的频率过高，控制电压会降低，降低 VCO 的输出频率。</li>
</ul>

<p><strong>分频器</strong> 用于将 VCO 输出的高频信号按比例降低，使其与参考信号的频率接近。分频器通常会将 VCO 的输出频率除以一个特定的整数值（N），然后将这一频率反馈给相位检测器。这使得 PLL 电路可以通过控制 VCO 的高频输出与低频的参考信号进行比对，从而调节 VCO 的输出频率。例如晶振的频率是 10MHz，而我们希望得到 100MHz 的输出时钟信号。通过将 VCO 输出的 100MHz 信号分频 10 倍，得到一个 10MHz 的信号，然后与晶振的 10MHz 信号进行相位比较。这种设计使得 VCO 的输出频率被锁定在晶振频率的整数倍上。</p>

<p>现代 PLL 电路中，通常会采用一些更加复杂的技术来实现更精确的频率控制和调节。这包括分数倍频和多级 PLL 串接。这些技术可以生成更加灵活、精确的时钟信号，但它们的原理和上文其实没有本质区别，因此在本文中我们不深入讨论它们。</p>

<h3 id="小结">小结</h3>

<p><img src="/assets/images/mos6502.jpeg" alt="MOS 6502" /></p>

<p>结合上面的知识，你已经几乎可以设计出类似于 1971 年的 Intel 4004 或者 1975 年的 MOS 6502 这样的经典芯片了。不过这样的芯片性能非常慢。在<a href="/2024/10/05/semiconductor-02">下个章节</a>我们会讨论如何通过改进芯片设计来改善运行性能。</p>]]></content><author><name>CodeRemixer</name></author><category term="半导体" /><category term="数字电路" /><category term="芯片" /><category term="电路" /><summary type="html"><![CDATA[半导体芯片是现代电子设备的核心，从电视遥控器到超级计算机，无不依赖于这些微小的硅片。然而，芯片设计和制造是一个复杂的过程，涉及深厚的物理、化学知识和精密的工程技术。本篇文章旨在为读者提供一个全景式的了解，涵盖从半导体材料的基本原理到数字电路设计，以及从芯片体系结构到生产工艺的各个环节。]]></summary></entry><entry><title type="html">基于 Prolog 的 SATB 四部和声生成器</title><link href="https://coderemixer.com/2024/07/21/satb-generator" rel="alternate" type="text/html" title="基于 Prolog 的 SATB 四部和声生成器" /><published>2024-07-21T17:30:00+08:00</published><updated>2024-07-21T17:30:00+08:00</updated><id>https://coderemixer.com/2024/07/21/satb-generator</id><content type="html" xml:base="https://coderemixer.com/2024/07/21/satb-generator"><![CDATA[<p>最近在研究 SATB 四部和声，然后感觉如果完全按照和声学教材里的内容，尤其是 18 世纪的和声学概念，几乎就是给了一大堆规则，然后写的是有把人当成一个栈机 (stack machine) 不停搜索不要违背这些规则。既然如此，为什么我们不能设计一个搜索程序，直接将四部和声的配法规约出来呢？</p>

<h2 id="prolog">Prolog</h2>

<p>很多年前，我写过一篇关于 Prolog 的<a href="/2020/01/18/archetypes-skeptic">博客</a>，Prolog 是一个非常方便的，给定一系列逻辑，然后让它从中规约出结果的一门语言。一开始其实我想和之前<a href="/2020/04/23/auto-blues-generator">某篇博客</a> 一样使用 Ruby 和 Sonic Pi 来实现，因为可以所见即所得的获得但发现随着规则越写越多，搜索的实现变得越来越麻烦。这时候就轮到我们 Prolog 出场了。</p>

<h2 id="和弦">和弦</h2>

<p>如果我们只考虑单个和弦，不处理和弦之间连接的话，我们先定义一个音符，是由其音名、升降调符号和所在的八度构成的，为了方便比较，我们不妨把这个音对应的 MIDI 调值算出来，于是我们可以先写一个这样的代码：</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">% Notes</span>
<span class="ss">key_note</span><span class="p">(</span><span class="ss">c</span><span class="p">).</span> <span class="ss">key_note</span><span class="p">(</span><span class="ss">d</span><span class="p">).</span> <span class="ss">key_note</span><span class="p">(</span><span class="ss">e</span><span class="p">).</span> <span class="ss">key_note</span><span class="p">(</span><span class="ss">g</span><span class="p">).</span> <span class="ss">key_note</span><span class="p">(</span><span class="ss">a</span><span class="p">).</span> <span class="ss">key_note</span><span class="p">(</span><span class="ss">b</span><span class="p">).</span>
<span class="ss">semitone_offset</span><span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">0</span><span class="p">).</span> <span class="ss">semitone_offset</span><span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">2</span><span class="p">).</span> <span class="ss">semitone_offset</span><span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">4</span><span class="p">).</span> <span class="ss">semitone_offset</span><span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">5</span><span class="p">).</span>
<span class="ss">semitone_offset</span><span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">7</span><span class="p">).</span> <span class="ss">semitone_offset</span><span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">9</span><span class="p">).</span> <span class="ss">semitone_offset</span><span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">11</span><span class="p">).</span>
<span class="ss">note_offset</span><span class="p">(</span><span class="o">-</span><span class="m">2</span><span class="p">).</span> <span class="ss">note_offset</span><span class="p">(</span><span class="o">-</span><span class="m">1</span><span class="p">).</span> <span class="ss">note_offset</span><span class="p">(</span><span class="m">0</span><span class="p">).</span> <span class="ss">note_offset</span><span class="p">(</span><span class="m">1</span><span class="p">).</span> <span class="ss">note_offset</span><span class="p">(</span><span class="m">2</span><span class="p">).</span>
<span class="ss">octave</span><span class="p">(</span><span class="o">-</span><span class="m">2</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="o">-</span><span class="m">1</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">0</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">1</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">2</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">3</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">4</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">5</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">6</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">7</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">8</span><span class="p">).</span> <span class="ss">octave</span><span class="p">(</span><span class="m">9</span><span class="p">).</span>

<span class="c1">% Define note/3 predicate to check if a given Key, Offset, and Octave form a valid note</span>
<span class="ss">note</span><span class="p">(</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">,</span> <span class="nv">Octave</span><span class="p">)</span> <span class="p">:-</span> <span class="ss">key_note</span><span class="p">(</span><span class="nv">Key</span><span class="p">),</span> <span class="ss">note_offset</span><span class="p">(</span><span class="nv">Offset</span><span class="p">),</span> <span class="ss">octave</span><span class="p">(</span><span class="nv">Octave</span><span class="p">).</span>

<span class="c1">% Define note_value/4 predicate to calculate the MIDI value for a given Key, Offset, and Octave</span>
<span class="ss">note_value</span><span class="p">(</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">,</span> <span class="nv">Octave</span><span class="p">,</span> <span class="nv">Value</span><span class="p">)</span> <span class="p">:-</span>
  <span class="ss">note</span><span class="p">(</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">,</span> <span class="nv">Octave</span><span class="p">),</span>
  <span class="ss">semitone_offset</span><span class="p">(</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">KeyOffset</span><span class="p">),</span>
  <span class="nv">Value</span> <span class="ss">is</span> <span class="m">12</span> <span class="o">*</span> <span class="p">(</span><span class="nv">Octave</span> <span class="o">+</span> <span class="m">1</span><span class="p">)</span> <span class="o">+</span> <span class="nv">KeyOffset</span> <span class="o">+</span> <span class="nv">Offset</span><span class="p">.</span>

<span class="c1">% Define note_value/2 predicate to calculate the MIDI value for a given note</span>
<span class="ss">note_value</span><span class="p">([</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">,</span> <span class="nv">Octave</span><span class="p">],</span> <span class="nv">Value</span><span class="p">)</span> <span class="p">:-</span> <span class="ss">note_value</span><span class="p">(</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">,</span> <span class="nv">Octave</span><span class="p">,</span> <span class="nv">Value</span><span class="p">).</span>
</code></pre></div></div>

<p>围绕这个系统，我们可以开始定义音域，我这里所使用的音域规则如下（括号之中的是 MIDI 调值）：</p>
<ol>
  <li>Soprano: C4 (60) - A5 (81)</li>
  <li>Alto: F3 (53) - D5 (74)</li>
  <li>Tenor: C3 (48) - A4 (69)</li>
  <li>Bass: F2 (41) - D4 (62)</li>
</ol>

<p>围绕这个机制，我们可以写出这样的规则：</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">voice</span><span class="p">(</span><span class="ss">soprano</span><span class="p">).</span> <span class="ss">voice</span><span class="p">(</span><span class="ss">alto</span><span class="p">).</span> <span class="ss">voice</span><span class="p">(</span><span class="ss">tenor</span><span class="p">).</span> <span class="ss">voice</span><span class="p">(</span><span class="ss">bass</span><span class="p">).</span>
<span class="ss">voice_range</span><span class="p">(</span><span class="ss">soprano</span><span class="p">,</span> <span class="m">60</span><span class="p">,</span> <span class="m">81</span><span class="p">).</span>
<span class="ss">voice_range</span><span class="p">(</span><span class="ss">alto</span><span class="p">,</span> <span class="m">53</span><span class="p">,</span> <span class="m">74</span><span class="p">).</span>
<span class="ss">voice_range</span><span class="p">(</span><span class="ss">tenor</span><span class="p">,</span> <span class="m">48</span><span class="p">,</span> <span class="m">69</span><span class="p">).</span>
<span class="ss">voice_range</span><span class="p">(</span><span class="ss">bass</span><span class="p">,</span> <span class="m">41</span><span class="p">,</span> <span class="m">62</span><span class="p">).</span>

<span class="c1">% Define legal_note_range/4 predicate to check if a note is within the range for a given voice</span>
<span class="ss">legal_note_range</span><span class="p">(</span><span class="nv">Voice</span><span class="p">,</span> <span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">,</span> <span class="nv">Octave</span><span class="p">)</span> <span class="p">:-</span>
  <span class="ss">voice</span><span class="p">(</span><span class="nv">Voice</span><span class="p">),</span> <span class="ss">note</span><span class="p">(</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">,</span> <span class="nv">Octave</span><span class="p">),</span>
  <span class="ss">voice_range</span><span class="p">(</span><span class="nv">Voice</span><span class="p">,</span> <span class="nv">Min</span><span class="p">,</span> <span class="nv">Max</span><span class="p">),</span> <span class="ss">note_value</span><span class="p">(</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">,</span> <span class="nv">Octave</span><span class="p">,</span> <span class="nv">Value</span><span class="p">),</span> <span class="nv">Value</span> <span class="o">&gt;=</span> <span class="nv">Min</span><span class="p">,</span> <span class="nv">Value</span> <span class="o">=&lt;</span> <span class="nv">Max</span><span class="p">.</span>
</code></pre></div></div>

<p>除了各个声部本身能支持的音高，我们还需要处理两个问题：SATB 四部必须是严格地，S 的音高大于 A 的音高大于 T 的音高大于 B 的音高，以及女高和女低音、女低音和男高音之间不能超过一个完全八度，男高音和男低音之间不能超过完全十二度，这时候我们计算的 MIDI 调值可以很方便于我们构建这个规则：</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">% Define legal_chord_range/4 predicate to check if a chord is within the ranges and respects voice leading</span>
<span class="ss">legal_chord_range</span><span class="p">(</span><span class="nv">Soprano</span><span class="p">,</span> <span class="nv">Alto</span><span class="p">,</span> <span class="nv">Tenor</span><span class="p">,</span> <span class="nv">Bass</span><span class="p">)</span> <span class="p">:-</span>
  <span class="nv">Soprano</span> <span class="o">=</span> <span class="p">[</span><span class="nv">SopranoKey</span><span class="p">,</span> <span class="nv">SopranoOffset</span><span class="p">,</span> <span class="nv">SopranoOctave</span><span class="p">],</span>
  <span class="nv">Alto</span> <span class="o">=</span> <span class="p">[</span><span class="nv">AltoKey</span><span class="p">,</span> <span class="nv">AltoOffset</span><span class="p">,</span> <span class="nv">AltoOctave</span><span class="p">],</span>
  <span class="nv">Tenor</span> <span class="o">=</span> <span class="p">[</span><span class="nv">TenorKey</span><span class="p">,</span> <span class="nv">TenorOffset</span><span class="p">,</span> <span class="nv">TenorOctave</span><span class="p">],</span>
  <span class="nv">Bass</span> <span class="o">=</span> <span class="p">[</span><span class="nv">BassKey</span><span class="p">,</span> <span class="nv">BassOffset</span><span class="p">,</span> <span class="nv">BassOctave</span><span class="p">],</span>
  <span class="ss">legal_note_range</span><span class="p">(</span><span class="ss">soprano</span><span class="p">,</span> <span class="nv">SopranoKey</span><span class="p">,</span> <span class="nv">SopranoOffset</span><span class="p">,</span> <span class="nv">SopranoOctave</span><span class="p">),</span>
  <span class="ss">legal_note_range</span><span class="p">(</span><span class="ss">alto</span><span class="p">,</span> <span class="nv">AltoKey</span><span class="p">,</span> <span class="nv">AltoOffset</span><span class="p">,</span> <span class="nv">AltoOctave</span><span class="p">),</span>
  <span class="ss">legal_note_range</span><span class="p">(</span><span class="ss">tenor</span><span class="p">,</span> <span class="nv">TenorKey</span><span class="p">,</span> <span class="nv">TenorOffset</span><span class="p">,</span> <span class="nv">TenorOctave</span><span class="p">),</span>
  <span class="ss">legal_note_range</span><span class="p">(</span><span class="ss">bass</span><span class="p">,</span> <span class="nv">BassKey</span><span class="p">,</span> <span class="nv">BassOffset</span><span class="p">,</span> <span class="nv">BassOctave</span><span class="p">),</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">SopranoKey</span><span class="p">,</span> <span class="nv">SopranoOffset</span><span class="p">,</span> <span class="nv">SopranoOctave</span><span class="p">,</span> <span class="nv">SopranoValue</span><span class="p">),</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">AltoKey</span><span class="p">,</span> <span class="nv">AltoOffset</span><span class="p">,</span> <span class="nv">AltoOctave</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">),</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">TenorKey</span><span class="p">,</span> <span class="nv">TenorOffset</span><span class="p">,</span> <span class="nv">TenorOctave</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">),</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">BassKey</span><span class="p">,</span> <span class="nv">BassOffset</span><span class="p">,</span> <span class="nv">BassOctave</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">),</span>
  <span class="c1">% Check for proper voice leading</span>
  <span class="nv">SopranoValue</span> <span class="o">&gt;</span> <span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">AltoValue</span> <span class="o">&gt;</span> <span class="nv">TenorValue</span><span class="p">,</span> <span class="nv">TenorValue</span> <span class="o">&gt;</span> <span class="nv">BassValue</span><span class="p">,</span>
  <span class="c1">% Check for proper voice spacing</span>
  <span class="nv">SopranoValue</span> <span class="o">-</span> <span class="nv">AltoValue</span> <span class="o">=&lt;</span> <span class="m">12</span><span class="p">,</span>
  <span class="nv">AltoValue</span> <span class="o">-</span> <span class="nv">TenorValue</span> <span class="o">=&lt;</span> <span class="m">12</span><span class="p">,</span>
  <span class="nv">TenorValue</span> <span class="o">-</span> <span class="nv">BassValue</span> <span class="o">=&lt;</span> <span class="m">19</span><span class="p">.</span>
</code></pre></div></div>

<p>写到这里我们需要处理一下重复音的问题，写四部和声的时候，当给定一个三和弦，至少会出现一个重复音，在一些情况下，我们也会跳过某个三度或者五度。而七和弦比较特殊，一般七和弦的七音是一定要出现的，但是不能重复出现。另外，三全音（增四度或减五度）只能出现一次，调中的七音也不能重复出现，于是我们需要处理一下这几种情况：</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">% Sclaes</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">cmaj</span><span class="p">,</span> <span class="p">[(</span><span class="ss">c</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">dmaj</span><span class="p">,</span> <span class="p">[(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">emaj</span><span class="p">,</span> <span class="p">[(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">fmaj</span><span class="p">,</span> <span class="p">[(</span><span class="ss">f</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="o">-</span><span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">gmaj</span><span class="p">,</span> <span class="p">[(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">amaj</span><span class="p">,</span> <span class="p">[(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">bmaj</span><span class="p">,</span> <span class="p">[(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">cmin</span><span class="p">,</span> <span class="p">[(</span><span class="ss">c</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="o">-</span><span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="o">-</span><span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="o">-</span><span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">dmin</span><span class="p">,</span> <span class="p">[(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">emin</span><span class="p">,</span> <span class="p">[(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">fmin</span><span class="p">,</span> <span class="p">[(</span><span class="ss">f</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="o">-</span><span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="o">-</span><span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="o">-</span><span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">gmin</span><span class="p">,</span> <span class="p">[(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">amin</span><span class="p">,</span> <span class="p">[(</span><span class="ss">a</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>
<span class="ss">scale</span><span class="p">(</span><span class="ss">bmin</span><span class="p">,</span> <span class="p">[(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">c</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">e</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">f</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">g</span><span class="p">,</span> <span class="m">1</span><span class="p">),</span> <span class="p">(</span><span class="ss">a</span><span class="p">,</span> <span class="m">1</span><span class="p">)]).</span>

<span class="c1">% Define is_augmented_fourth/2 predicate to check if two notes are an augmented fourth apart</span>
<span class="ss">is_augmented_fourth</span><span class="p">(</span><span class="ss">note_value</span><span class="p">(</span><span class="nv">Value1</span><span class="p">),</span> <span class="ss">note_value</span><span class="p">(</span><span class="nv">Value2</span><span class="p">))</span> <span class="p">:-</span>
  <span class="nv">Interval</span> <span class="ss">is</span> <span class="ss">abs</span><span class="p">(</span><span class="nv">Value1</span> <span class="o">-</span> <span class="nv">Value2</span><span class="p">),</span>
  <span class="nv">Interval</span> <span class="o">=:=</span> <span class="m">6</span><span class="p">.</span>

<span class="ss">is_seventh_note_of_scale</span><span class="p">(</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">,</span> <span class="nv">Scale</span><span class="p">)</span> <span class="p">:-</span>
  <span class="ss">scale</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="nv">Notes</span><span class="p">),</span>
  <span class="ss">last</span><span class="p">(</span><span class="nv">Notes</span><span class="p">,</span> <span class="p">(</span><span class="nv">Key</span><span class="p">,</span> <span class="nv">Offset</span><span class="p">)).</span>

<span class="c1">% Define is_same_note_value/2 predicate to check if two notes have the same MIDI value</span>
<span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">Value1</span><span class="p">,</span> <span class="nv">Value2</span><span class="p">)</span> <span class="p">:-</span>
  <span class="nv">Value1</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="nv">Value2</span> <span class="o">//</span> <span class="m">12</span><span class="p">.</span>

<span class="c1">% Define legal_chord_repeat/4 predicate to check if a chord has repeated notes</span>
<span class="ss">legal_chord_repeat</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="nv">Soprano</span><span class="p">,</span> <span class="nv">Alto</span><span class="p">,</span> <span class="nv">Tenor</span><span class="p">,</span> <span class="nv">Bass</span><span class="p">)</span> <span class="p">:-</span>
  <span class="nv">Soprano</span> <span class="o">=</span> <span class="p">[</span><span class="nv">SopranoKey</span><span class="p">,</span> <span class="nv">SopranoOffset</span><span class="p">,</span> <span class="nv">SopranoOctave</span><span class="p">],</span>
  <span class="nv">Alto</span> <span class="o">=</span> <span class="p">[</span><span class="nv">AltoKey</span><span class="p">,</span> <span class="nv">AltoOffset</span><span class="p">,</span> <span class="nv">AltoOctave</span><span class="p">],</span>
  <span class="nv">Tenor</span> <span class="o">=</span> <span class="p">[</span><span class="nv">TenorKey</span><span class="p">,</span> <span class="nv">TenorOffset</span><span class="p">,</span> <span class="nv">TenorOctave</span><span class="p">],</span>
  <span class="nv">Bass</span> <span class="o">=</span> <span class="p">[</span><span class="nv">BassKey</span><span class="p">,</span> <span class="nv">BassOffset</span><span class="p">,</span> <span class="nv">BassOctave</span><span class="p">],</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">SopranoKey</span><span class="p">,</span> <span class="nv">SopranoOffset</span><span class="p">,</span> <span class="nv">SopranoOctave</span><span class="p">,</span> <span class="nv">SopranoValue</span><span class="p">),</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">AltoKey</span><span class="p">,</span> <span class="nv">AltoOffset</span><span class="p">,</span> <span class="nv">AltoOctave</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">),</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">TenorKey</span><span class="p">,</span> <span class="nv">TenorOffset</span><span class="p">,</span> <span class="nv">TenorOctave</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">),</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">BassKey</span><span class="p">,</span> <span class="nv">BassOffset</span><span class="p">,</span> <span class="nv">BassOctave</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">),</span>
  <span class="c1">% Check for argumented fourth</span>
  <span class="c1">% if the Soprano and Alto are argumented fourth apart, then the Tenor and Base should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_augmented_fourth</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">))),</span>
  <span class="c1">% if the Soporano and Tenor are argumented fourth apart, then the Alto and Base should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_augmented_fourth</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">TenorValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">TenorValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">))),</span>
  <span class="c1">% if the Soprano and Bass are argumented fourth apart, then the Alto and Tenor should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_augmented_fourth</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">BassValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">BassValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">))),</span>
  <span class="c1">% if the Alto and Tenor are argumented fourth apart, then the Soprano and Bass should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_augmented_fourth</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">BassValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">))),</span>
  <span class="c1">% if the Alto and Bass are argumented fourth apart, then the Soprano and Tenor should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_augmented_fourth</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">BassValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">))),</span>
  <span class="c1">% if the Tenor and Bass are argumented fourth apart, then the Soprano and Alto should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_augmented_fourth</span><span class="p">(</span><span class="nv">TenorValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">))),</span>
  <span class="c1">% Check for if it is the seventh note of the scale</span>
  <span class="c1">% if the Soprano is the seventh note of the scale, then the Alto, Tenor, and Bass should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_seventh_note_of_scale</span><span class="p">(</span><span class="nv">SopranoKey</span><span class="p">,</span> <span class="nv">SopranoOffset</span><span class="p">,</span> <span class="nv">Scale</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">SopranoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">))),</span>
  <span class="c1">% if the Alto is the seventh note of the scale, then the Soprano, Tenor, and Bass should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_seventh_note_of_scale</span><span class="p">(</span><span class="nv">AltoKey</span><span class="p">,</span> <span class="nv">AltoOffset</span><span class="p">,</span> <span class="nv">Scale</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">SopranoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">AltoValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">))),</span>
  <span class="c1">% if the Tenor is the seventh note of the scale, then the Soprano, Alto, and Bass should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_seventh_note_of_scale</span><span class="p">(</span><span class="nv">TenorKey</span><span class="p">,</span> <span class="nv">TenorOffset</span><span class="p">,</span> <span class="nv">Scale</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">TenorValue</span><span class="p">,</span> <span class="nv">SopranoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">TenorValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">TenorValue</span><span class="p">,</span> <span class="nv">BassValue</span><span class="p">))),</span>
  <span class="c1">% if the Bass is the seventh note of the scale, then the Soprano, Alto, and Tenor should not use the same note</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="ss">is_seventh_note_of_scale</span><span class="p">(</span><span class="nv">BassKey</span><span class="p">,</span> <span class="nv">BassOffset</span><span class="p">,</span> <span class="nv">Scale</span><span class="p">),</span> <span class="p">(</span><span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">BassValue</span><span class="p">,</span> <span class="nv">SopranoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">BassValue</span><span class="p">,</span> <span class="nv">AltoValue</span><span class="p">);</span> <span class="ss">is_same_note_value</span><span class="p">(</span><span class="nv">BassValue</span><span class="p">,</span> <span class="nv">TenorValue</span><span class="p">))).</span>  

<span class="ss">legal_chord_contents</span><span class="p">(</span><span class="nv">Chord</span><span class="p">,</span> <span class="nv">Soprano</span><span class="p">,</span> <span class="nv">Alto</span><span class="p">,</span> <span class="nv">Tenor</span><span class="p">,</span> <span class="nv">Bass</span><span class="p">)</span> <span class="p">:-</span>
  <span class="nv">Soprano</span> <span class="o">=</span> <span class="p">[</span><span class="nv">SopranoKey</span><span class="p">,</span> <span class="nv">SopranoOffset</span><span class="p">,</span> <span class="nv">_</span><span class="p">],</span>
  <span class="nv">Alto</span> <span class="o">=</span> <span class="p">[</span><span class="nv">AltoKey</span><span class="p">,</span> <span class="nv">AltoOffset</span><span class="p">,</span> <span class="nv">_</span><span class="p">],</span>
  <span class="nv">Tenor</span> <span class="o">=</span> <span class="p">[</span><span class="nv">TenorKey</span><span class="p">,</span> <span class="nv">TenorOffset</span><span class="p">,</span> <span class="nv">_</span><span class="p">],</span>
  <span class="nv">Bass</span> <span class="o">=</span> <span class="p">[</span><span class="nv">BassKey</span><span class="p">,</span> <span class="nv">BassOffset</span><span class="p">,</span> <span class="nv">_</span><span class="p">],</span>
  <span class="nv">SopranoSignature</span> <span class="o">=</span> <span class="p">(</span><span class="nv">SopranoKey</span><span class="p">,</span> <span class="nv">SopranoOffset</span><span class="p">),</span>
  <span class="nv">AltoSignature</span> <span class="o">=</span> <span class="p">(</span><span class="nv">AltoKey</span><span class="p">,</span> <span class="nv">AltoOffset</span><span class="p">),</span>
  <span class="nv">TenorSignature</span> <span class="o">=</span> <span class="p">(</span><span class="nv">TenorKey</span><span class="p">,</span> <span class="nv">TenorOffset</span><span class="p">),</span>
  <span class="nv">BassSignature</span> <span class="o">=</span> <span class="p">(</span><span class="nv">BassKey</span><span class="p">,</span> <span class="nv">BassOffset</span><span class="p">),</span>
  <span class="c1">% all four parts should be in the chord</span>
  <span class="ss">member</span><span class="p">(</span><span class="nv">SopranoSignature</span><span class="p">,</span> <span class="nv">Chord</span><span class="p">),</span> <span class="ss">member</span><span class="p">(</span><span class="nv">AltoSignature</span><span class="p">,</span> <span class="nv">Chord</span><span class="p">),</span> <span class="ss">member</span><span class="p">(</span><span class="nv">TenorSignature</span><span class="p">,</span> <span class="nv">Chord</span><span class="p">),</span> <span class="ss">member</span><span class="p">(</span><span class="nv">BassSignature</span><span class="p">,</span> <span class="nv">Chord</span><span class="p">),</span>
  <span class="c1">% if the chord is a seventh chord (four members), do not repeat the seventh note</span>
  <span class="p">(</span><span class="ss">length</span><span class="p">(</span><span class="nv">Chord</span><span class="p">,</span> <span class="nv">ChordSize</span><span class="p">),</span> <span class="p">(</span><span class="nv">ChordSize</span> <span class="o">=</span><span class="err">\</span><span class="o">=</span> <span class="m">4</span><span class="p">;</span> <span class="p">(</span>
    <span class="p">(</span><span class="ss">last</span><span class="p">(</span><span class="nv">Chord</span><span class="p">,</span> <span class="nv">SecenthNote</span><span class="p">),</span>
    <span class="c1">% length [SopranoSignature, AltoSignature, TenorSignature, BassSignature] whose note is the same as the seventh note</span>
    <span class="ss">exclude</span><span class="p">(</span><span class="o">=</span><span class="p">(</span><span class="nv">SecenthNote</span><span class="p">),</span> <span class="p">[</span><span class="nv">SopranoSignature</span><span class="p">,</span> <span class="nv">AltoSignature</span><span class="p">,</span> <span class="nv">TenorSignature</span><span class="p">,</span> <span class="nv">BassSignature</span><span class="p">],</span> <span class="nv">FilteredSeventhNote</span><span class="p">),</span>
    <span class="ss">length</span><span class="p">(</span><span class="nv">FilteredSeventhNote</span><span class="p">,</span> <span class="nv">FilteredSeventhNoteSize</span><span class="p">),</span> <span class="m">4</span> <span class="o">-</span> <span class="nv">FilteredSeventhNoteSize</span> <span class="o">=:=</span> <span class="m">1</span><span class="p">)))).</span>
</code></pre></div></div>

<p>现在我们把这些东西组合起来，不妨来试试效果：</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">% Combination of all predicates to check if a chord is legal</span>
<span class="ss">legal_chord</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="nv">Chord</span><span class="p">,</span> <span class="nv">Soprano</span><span class="p">,</span> <span class="nv">Alto</span><span class="p">,</span> <span class="nv">Tenor</span><span class="p">,</span> <span class="nv">Bass</span><span class="p">)</span> <span class="p">:-</span>
  <span class="ss">legal_chord_range</span><span class="p">(</span><span class="nv">Soprano</span><span class="p">,</span> <span class="nv">Alto</span><span class="p">,</span> <span class="nv">Tenor</span><span class="p">,</span> <span class="nv">Bass</span><span class="p">),</span>
  <span class="ss">legal_chord_repeat</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="nv">Soprano</span><span class="p">,</span> <span class="nv">Alto</span><span class="p">,</span> <span class="nv">Tenor</span><span class="p">,</span> <span class="nv">Bass</span><span class="p">),</span>
  <span class="ss">legal_chord_contents</span><span class="p">(</span><span class="nv">Chord</span><span class="p">,</span> <span class="nv">Soprano</span><span class="p">,</span> <span class="nv">Alto</span><span class="p">,</span> <span class="nv">Tenor</span><span class="p">,</span> <span class="nv">Bass</span><span class="p">).</span>
</code></pre></div></div>

<p>我们给定一个 G 大调下的一组 G 和弦，让它判断这是不是非法的：</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">?-</span> <span class="ss">legal_chord</span><span class="p">(</span><span class="ss">gmaj</span><span class="p">,</span> <span class="p">[(</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">),</span> <span class="p">(</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">)],</span> <span class="p">[</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">5</span><span class="p">],</span> <span class="p">[</span><span class="ss">b</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">4</span><span class="p">],</span> <span class="p">[</span><span class="ss">d</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">4</span><span class="p">],</span> <span class="p">[</span><span class="ss">g</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">2</span><span class="p">]).</span>
<span class="ss">true</span><span class="p">.</span>
</code></pre></div></div>

<p>非常好！</p>

<h2 id="连接">连接</h2>

<p>连接的问题比和弦本身更麻烦，我们有六个问题需要解决：</p>
<ol>
  <li>避免四个声部沿着完全相同方向移动</li>
  <li>避免声部超越（也就是两个连续的、包含至少两个独立声部的和音，当后和音中较低的声部的音比前和音中较高的声部的音要高，或者当后和音中较高的声部的音比前和音中较低的声部的音要低）</li>
  <li>避免出现方向相同的平行八度</li>
  <li>避免出现方向相同的平行五度</li>
  <li>避免出现隐伏八度</li>
  <li>避免出现隐伏五度</li>
</ol>

<p>写出来大概是这样的：</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">legal_connection</span><span class="p">(</span><span class="nv">Chord1</span><span class="p">,</span> <span class="nv">Chord2</span><span class="p">)</span> <span class="p">:-</span>
  <span class="nv">Chord1</span> <span class="o">=</span> <span class="p">[</span><span class="nv">Soprano1</span><span class="p">,</span> <span class="nv">Alto1</span><span class="p">,</span> <span class="nv">Tenor1</span><span class="p">,</span> <span class="nv">Bass1</span><span class="p">],</span>
  <span class="nv">Chord2</span> <span class="o">=</span> <span class="p">[</span><span class="nv">Soprano2</span><span class="p">,</span> <span class="nv">Alto2</span><span class="p">,</span> <span class="nv">Tenor2</span><span class="p">,</span> <span class="nv">Bass2</span><span class="p">],</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">Soprano1</span><span class="p">,</span> <span class="nv">SopranoValue1</span><span class="p">),</span> <span class="ss">note_value</span><span class="p">(</span><span class="nv">Alto1</span><span class="p">,</span> <span class="nv">AltoValue1</span><span class="p">),</span> <span class="ss">note_value</span><span class="p">(</span><span class="nv">Tenor1</span><span class="p">,</span> <span class="nv">TenorValue1</span><span class="p">),</span> <span class="ss">note_value</span><span class="p">(</span><span class="nv">Bass1</span><span class="p">,</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="ss">note_value</span><span class="p">(</span><span class="nv">Soprano2</span><span class="p">,</span> <span class="nv">SopranoValue2</span><span class="p">),</span> <span class="ss">note_value</span><span class="p">(</span><span class="nv">Alto2</span><span class="p">,</span> <span class="nv">AltoValue2</span><span class="p">),</span> <span class="ss">note_value</span><span class="p">(</span><span class="nv">Tenor2</span><span class="p">,</span> <span class="nv">TenorValue2</span><span class="p">),</span> <span class="ss">note_value</span><span class="p">(</span><span class="nv">Bass2</span><span class="p">,</span> <span class="nv">BassValue2</span><span class="p">),</span>
  <span class="c1">% all four parts should not move in parallel motion</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="nv">SopranoValue1</span> <span class="o">&gt;</span> <span class="nv">SopranoValue2</span><span class="p">,</span> <span class="nv">AltoValue1</span> <span class="o">&gt;</span> <span class="nv">AltoValue2</span><span class="p">,</span> <span class="nv">TenorValue1</span> <span class="o">&gt;</span> <span class="nv">TenorValue2</span><span class="p">,</span> <span class="nv">BassValue1</span> <span class="o">&gt;</span> <span class="nv">BassValue2</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="nv">SopranoValue1</span> <span class="o">&lt;</span> <span class="nv">SopranoValue2</span><span class="p">,</span> <span class="nv">AltoValue1</span> <span class="o">&lt;</span> <span class="nv">AltoValue2</span><span class="p">,</span> <span class="nv">TenorValue1</span> <span class="o">&lt;</span> <span class="nv">TenorValue2</span><span class="p">,</span> <span class="nv">BassValue1</span> <span class="o">&lt;</span> <span class="nv">BassValue2</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="nv">SopranoValue1</span> <span class="o">=</span> <span class="nv">SopranoValue2</span><span class="p">,</span> <span class="nv">AltoValue1</span> <span class="o">=</span> <span class="nv">AltoValue2</span><span class="p">,</span> <span class="nv">TenorValue1</span> <span class="o">=</span> <span class="nv">TenorValue2</span><span class="p">,</span> <span class="nv">BassValue1</span> <span class="o">=</span> <span class="nv">BassValue2</span><span class="p">),</span>
  <span class="c1">% avoid voice overlapping</span>
  <span class="nv">SopranoValue2</span> <span class="o">&gt;=</span> <span class="nv">AltoValue1</span><span class="p">,</span>
  <span class="nv">AltoValue2</span> <span class="o">&gt;=</span> <span class="nv">TenorValue1</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">=&lt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span>
  <span class="nv">TenorValue2</span> <span class="o">&gt;=</span> <span class="nv">BassValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">=&lt;</span> <span class="nv">AltoValue1</span><span class="p">,</span>
  <span class="nv">BassValue2</span> <span class="o">=&lt;</span> <span class="nv">TenorValue1</span><span class="p">,</span>
  <span class="c1">% no parallel 8th</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">AltoValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">AltoValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&gt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&gt;</span> <span class="nv">AltoValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">AltoValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">AltoValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&lt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&lt;</span> <span class="nv">AltoValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">AltoValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">AltoValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">=</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">=</span> <span class="nv">AltoValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&gt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&gt;</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&lt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&lt;</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">=</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">=</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&gt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&gt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&lt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&lt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">=</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">=</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&gt;</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&gt;</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&lt;</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&lt;</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">=</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">=</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&gt;</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&gt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&lt;</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&lt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">=</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">=</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">TenorValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">TenorValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&gt;</span> <span class="nv">TenorValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&gt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">TenorValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">TenorValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&lt;</span> <span class="nv">TenorValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&lt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">TenorValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">TenorValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">=</span> <span class="nv">TenorValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">=</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="c1">% % no parallel 5th</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">AltoValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">AltoValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&gt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&gt;</span> <span class="nv">AltoValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">AltoValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">AltoValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&lt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&lt;</span> <span class="nv">AltoValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">AltoValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">AltoValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">=</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">=</span> <span class="nv">AltoValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&gt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&gt;</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&lt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&lt;</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">=</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">=</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&gt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&gt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">&lt;</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&lt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">SopranoValue2</span> <span class="o">=</span> <span class="nv">SopranoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">=</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&gt;</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&gt;</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&lt;</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&lt;</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">TenorValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">TenorValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">=</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">=</span> <span class="nv">TenorValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&gt;</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&gt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">&lt;</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&lt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">AltoValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">AltoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">AltoValue2</span> <span class="o">=</span> <span class="nv">AltoValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">=</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">TenorValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">TenorValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&gt;</span> <span class="nv">TenorValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&gt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">TenorValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">TenorValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">&lt;</span> <span class="nv">TenorValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&lt;</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">((</span><span class="nv">TenorValue1</span> <span class="o">-</span> <span class="nv">BassValue1</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="p">(</span><span class="nv">TenorValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">,</span> <span class="nv">TenorValue2</span> <span class="o">=</span> <span class="nv">TenorValue1</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">=</span> <span class="nv">BassValue1</span><span class="p">),</span>
  <span class="c1">% % no hidden 8th</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">SopranoValue1</span> <span class="o">&gt;</span> <span class="m">5</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&gt;</span> <span class="nv">BassValue1</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">SopranoValue2</span> <span class="o">&gt;</span> <span class="m">5</span><span class="p">,</span> <span class="nv">BassValue1</span> <span class="o">&gt;</span> <span class="nv">BassValue2</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">12</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">),</span>
  <span class="c1">% % no hidden 5th</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">SopranoValue1</span> <span class="o">&gt;</span> <span class="m">5</span><span class="p">,</span> <span class="nv">BassValue2</span> <span class="o">&gt;</span> <span class="nv">BassValue1</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">),</span>
  <span class="err">\</span><span class="o">+</span> <span class="p">(</span><span class="nv">SopranoValue1</span> <span class="o">-</span> <span class="nv">SopranoValue2</span> <span class="o">&gt;</span> <span class="m">5</span><span class="p">,</span> <span class="nv">BassValue1</span> <span class="o">&gt;</span> <span class="nv">BassValue2</span><span class="p">,</span> <span class="p">(</span><span class="nv">SopranoValue2</span> <span class="o">-</span> <span class="nv">BassValue2</span><span class="p">)</span> <span class="o">//</span> <span class="m">7</span> <span class="o">=:=</span> <span class="m">0</span><span class="p">).</span>
</code></pre></div></div>

<h2 id="尝试生成">尝试生成</h2>

<p>现在我们有了判断连接和和弦的东西，我们只需要给出低音声部和和弦，接下来就可以让 Prolog 全自动生成四部和声了，</p>

<div class="language-prolog highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ss">generate_chord</span><span class="p">(</span><span class="nv">_</span><span class="p">,</span> <span class="p">[]).</span>
<span class="ss">generate_chord</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="p">[</span><span class="nv">Ans1</span><span class="p">,</span> <span class="nv">Ans2</span> <span class="p">|</span> <span class="nv">Rest</span><span class="p">])</span> <span class="p">:-</span>
  <span class="nv">Ans1</span> <span class="o">=</span> <span class="p">[</span><span class="nv">Chord1</span><span class="p">,</span> <span class="nv">Soprano1</span><span class="p">,</span> <span class="nv">Alto1</span><span class="p">,</span> <span class="nv">Tenor1</span><span class="p">,</span> <span class="nv">Bass1</span><span class="p">],</span>
  <span class="nv">Ans2</span> <span class="o">=</span> <span class="p">[</span><span class="nv">Chord2</span><span class="p">,</span> <span class="nv">Soprano2</span><span class="p">,</span> <span class="nv">Alto2</span><span class="p">,</span> <span class="nv">Tenor2</span><span class="p">,</span> <span class="nv">Bass2</span><span class="p">],</span>
  <span class="ss">legal_chord</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="nv">Chord1</span><span class="p">,</span> <span class="nv">Soprano1</span><span class="p">,</span> <span class="nv">Alto1</span><span class="p">,</span> <span class="nv">Tenor1</span><span class="p">,</span> <span class="nv">Bass1</span><span class="p">),</span>
  <span class="ss">legal_chord</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="nv">Chord2</span><span class="p">,</span> <span class="nv">Soprano2</span><span class="p">,</span> <span class="nv">Alto2</span><span class="p">,</span> <span class="nv">Tenor2</span><span class="p">,</span> <span class="nv">Bass2</span><span class="p">),</span>
  <span class="ss">legal_connection</span><span class="p">([</span><span class="nv">Soprano1</span><span class="p">,</span> <span class="nv">Alto1</span><span class="p">,</span> <span class="nv">Tenor1</span><span class="p">,</span> <span class="nv">Bass1</span><span class="p">],</span> <span class="p">[</span><span class="nv">Soprano2</span><span class="p">,</span> <span class="nv">Alto2</span><span class="p">,</span> <span class="nv">Tenor2</span><span class="p">,</span> <span class="nv">Bass2</span><span class="p">]),</span>
  <span class="ss">generate_chord</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="p">[</span><span class="nv">Ans2</span> <span class="p">|</span> <span class="nv">Rest</span><span class="p">]).</span>
<span class="ss">generate_chord</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="p">[</span><span class="nv">Ans</span><span class="p">])</span> <span class="p">:-</span>
  <span class="nv">Ans</span> <span class="o">=</span> <span class="p">[</span><span class="nv">Chord</span><span class="p">,</span> <span class="nv">Soprano</span><span class="p">,</span> <span class="nv">Alto</span><span class="p">,</span> <span class="nv">Tenor</span><span class="p">,</span> <span class="nv">Bass</span><span class="p">],</span>
  <span class="ss">legal_chord</span><span class="p">(</span><span class="nv">Scale</span><span class="p">,</span> <span class="nv">Chord</span><span class="p">,</span> <span class="nv">Soprano</span><span class="p">,</span> <span class="nv">Alto</span><span class="p">,</span> <span class="nv">Tenor</span><span class="p">,</span> <span class="nv">Bass</span><span class="p">).</span>
</code></pre></div></div>

<p>我从网上找了个例题，题目如下：</p>

<p><img src="/assets/images/satb-example-question.png" alt="SATB 样例题目" /></p>

<p>Prolog 这么做还是有一些缺陷，比如它不会总是选那个离自己最近的音来过渡，其实可以让 Prolog 把可能性都生成出来，然后在外面写一个某种评估函数进行排序其实就能解决问题，我这里就直接手动挑选了比较正常的结果，确实也不累。</p>

<p>下面请欣赏 Prolog 生成的四部和声：</p>

<p><img src="/assets/images/satb-example-answer.png" alt="SATB 生成答案" /></p>

<audio src="/assets/audio/satb-generated.mp3" controls=""></audio>

<p>您还别说，还别说，真的还行其实。</p>]]></content><author><name>CodeRemixer</name></author><category term="Prolog" /><category term="逻辑学" /><category term="音乐" /><category term="乐理" /><category term="合唱" /><summary type="html"><![CDATA[最近在研究 SATB 四部和声，然后感觉如果完全按照和声学教材里的内容，尤其是 18 世纪的和声学概念，几乎就是给了一大堆规则，然后写的是有把人当成一个栈机 (stack machine) 不停搜索不要违背这些规则。既然如此，为什么我们不能设计一个搜索程序，直接将四部和声的配法规约出来呢？]]></summary></entry><entry><title type="html">为什么没有三面体？</title><link href="https://coderemixer.com/2024/06/26/trihedron" rel="alternate" type="text/html" title="为什么没有三面体？" /><published>2024-06-26T09:22:00+08:00</published><updated>2024-06-26T09:22:00+08:00</updated><id>https://coderemixer.com/2024/06/26/trihedron</id><content type="html" xml:base="https://coderemixer.com/2024/06/26/trihedron"><![CDATA[<p>前几天我在桌子上放了一个骰塔，塔里放了一组 DnD 骰子。有个同事走过来看到一个正四面体的骰子问：「你这是三面骰吗？」我一愣，第一反应是，这世界上不应该存在三面体啊。不过一细想，为什么没有三面体确实是一个值得思考的好问题。</p>

<h2 id="欧拉示性数">欧拉示性数</h2>

<p>考虑到我们这需要讨论面的数量，第一个会想要用的公式显然是欧拉示性数公式，即：</p>

\[V-E+F=2\]

<p>欧拉示性数是一个拓扑不变量，即对于所有和一个球面同胚的多面体（三维空间中的凸多面体），都适用于这个公式。其中 \(V,E,F\) 分别是顶点、棱、面的个数。带入 \(F\) 的数字我们得到：</p>

\[V-E=-1\]

<p>不过光有这一个公式似乎不足以解释我们的问题。</p>

<h2 id="凸正多面体">凸正多面体</h2>

<p>如果我们简化一下这个问题，由于是骰子，我们可以先考虑三维空间中的凸正多面体。这个问题古希腊人就有所研究。由于每条棱有两个顶点，且在两个面上。我们定义每个面有 \(p\) 条棱，经过每个顶点会有 \(q\) 条棱，围绕棱可以得到下面的式子：</p>

\[pF=2E=qV\]

<p>带入数字 3，得到：</p>

\[3p=2E=qV\]

<p>我们有三个方程，但是有四个未知数，显然这个问题还是不太好解。好消息是我们知道 \(p,q,E,V\) 都需要是正整数这个条件。我们将 \(V\) 用 \(q\) 代换，能得到：</p>

\[\frac{2E}{q}=-1+E\]

<p>将等式两边同处以 \(2E\)，得到：</p>

\[\frac{1}{q}=-\frac{1}{2E}+\frac{1}{2}\]

<p>不妨将 \(2E\) 用 \(3p\) 替代，我们会得到：</p>

\[\frac{1}{q}+\frac{1}{3p}=\frac{1}{2}\]

<p>由于，\(p\) 和 \(q\) 都是正整数，显然地，\(p\) 越大，\(q\) 越小。我们取 \(p\) 最小值 1，有 \(q = 6\) ，此时 \(V = 1/2\) 不满足。同理，我们依次枚举调大 \(p\) 的值，直到 \(q &lt; 1\) 时，都找不到满足条件的整数解。因此不存在正三面体。</p>

<h2 id="凸多面体">凸多面体</h2>

<p>然而我们一开始想讨论的问题是是否存在三面体，而不是加上限定条件的凸正多面体，好像是答非所问了。而且并非所有的骰子都需要是凸正多面体，比如 d10 骰子就不是凸正多面体：</p>

<p><img src="/assets/images/dice-d10.png" alt="D10 骰子怎么看也不是正多面体" /></p>

<p>D10 骰子怎么看也不是正多面体</p>

<p>我们应该有一个更一般的处理三面体问题的结论。</p>

<p>和二维图形至少需要 3 个顶点类似，面为平面的多面体，至少需要 4 个顶点才能够形成有体积的三维图形。 而 4 个顶点构成的凸多边形就是凸四面体。只考虑这个问题实际上比上面的正凸多面体反而更简单了。</p>

<h2 id="多面体">多面体</h2>

<p>等等，好像我们也没有说我们的三面体得是凸的吧？我们知道所有和一个球面同胚的多面体（三维空间中的凸多面体）的欧拉示性数都是 2，但我们并没有说我们需要和球面同胚吧？比如马克杯和甜甜圈的欧拉示性数就是 0。</p>

<p><img src="/assets/images/mug-donuts.jpg" alt="拓扑学家常常分不清马克杯和甜甜圈" /></p>

<p>拓扑学家常常分不清马克杯和甜甜圈</p>

<p>我们能找到更一般地结论证明三面体不存在吧？不能，因为三面体可以存在，下面给出一个反例：</p>

<p><strong>一个截面为三角形的甜甜圈，它是不是一个三面体？</strong></p>

<p>很可惜的是「需要 4 个顶点才能够形成有体积的三维图形」的结论并没有发生改变，通过马克杯构造出来的三面体需要环面的参与才能够完成。但如果我们可以接受环面，我们能不能进一步接受曲面？也许问题可以变得更简单一些，比如圆柱体是否可以看成一个三面体？</p>

<h2 id="结论">结论</h2>

<p>好吧，其实我一开始的想法想当然了，我们可以有三面体，只是… 可能不那么容易做成骰子。</p>

<p>另外如果我们只从做骰子的角度来考虑的话，我们也并不需要三面体，比如我们将两个正四面体贴合在一起，将对称的两侧都标记为相同的数字的话，也是可以实现三面效果的骰子的。但脱离骰子本身而去重新考虑多面体的意义是一个非常不错的拓扑学训练。</p>

<h2 id="扩展阅读">扩展阅读</h2>

<ul>
  <li><a href="https://youtu.be/YtkIWDE36qU?si=3dZG91P4wCDEgMsE">This pattern breaks, but for a good reason - 3Blue1Brown</a></li>
  <li><a href="https://youtu.be/VvCytJvd4H0?si=7QxzQuQHg2EvHFGj">Why this puzzle is impossible - 3Blue1Brown</a></li>
</ul>]]></content><author><name>CodeRemixer</name></author><category term="拓扑学" /><category term="欧拉" /><category term="欧拉示性数" /><summary type="html"><![CDATA[前几天我在桌子上放了一个骰塔，塔里放了一组 DnD 骰子。有个同事走过来看到一个正四面体的骰子问：「你这是三面骰吗？」我一愣，第一反应是，这世界上不应该存在三面体啊。不过一细想，为什么没有三面体确实是一个值得思考的好问题。]]></summary></entry></feed>