[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [fulldecent]\ncustom: [\"https://www.paypal.me/fulldecent\", \"https://amazon.com/hz/wishlist/ls/EE78A23EEGQB\"]\n"
  },
  {
    "path": ".github/workflows/tests.yml",
    "content": "name: Check Lines Script\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  run-check-lines:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Run check-lines.sh script\n        run: ./tests/check-test-data-tabs.sh"
  },
  {
    "path": ".gitignore",
    "content": "# Object files\n*.o\n*.ko\n*.obj\n*.elf\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Libraries\n*.lib\n*.a\n*.la\n*.lo\n\n# Shared objects (inc. Windows DLLs)\n*.dll\n*.so\n*.so.*\n*.dylib\n\n# Executables\n*.exe\n*.out\n*.app\n*.i*86\n*.x86_64\n*.hex\n\n# Debug files\n*.dSYM/\n\n/main\nmain\ngmain\ncmain\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 William Entriken\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# System Bus Radio\n\nThis program transmits radio on computers / phones without radio transmitting hardware.\n\n## Thank you to our sponsors\n\n<img src=\"https://radiostay.com/images/logo.svg\" width=200>\n\nListen to online radio - <https://radiostay.com/>\n\n## Why?\n\nSome computers are intentionally disconnected from the rest of the world. This includes having their internet, wireless, bluetooth, USB, external file storage and audio capabilities removed. This is called \"air gapping\". Even in such a situation, this program can transmit radio.\n\nPublicly available documents already discuss exfiltration from secured systems using various electromagnetic radiations. This is documented in the TEMPEST guidelines published by the US National Security Agency and the US Department of Defense. This project simply adds to that discussion.\n\n## How to use it\n\n**NEW:** Try it in your browser, click here: <http://fulldecent.github.io/system-bus-radio/>\n\nEnter the implementations folder, select any of them and compile using `make`.\n\n```sh\nmake\n```\n\nRun this using a 2015 model MacBook Air. Then use a Sony STR-K670P radio receiver with the included antenna and tune it to 1580 kHz on AM.\n\nRun it and reference the a tune file or make your own!\n\n```sh\n./main ../../tunes/mary_had_a_little_lamb.tune\n```\n\nYou should hear the \"Mary Had a Little Lamb\" tune playing repeatedly. Other equipment and tuning may work as well. On the equipment above, the author has achieved clear transmission over two meters of open air or one meter through drywall. Different results will be achievable with different equipment.\n\nAre you using an antenna? At the beginning, the author placed the antenna directly on top of the number 4 key and that worked best (on any AM frequency). It was a round antenna. Then once they knew it worked they moved the antenna back. Moving it back reduced the number of frequencies that it worked on, and eventually only that one (1580 kHz) worked. Different hardware will certainly have different frequency response. Here are some results that have been sent in by readers. Please mail <github.com@phor.net> with your results (including makes and models of all equipment involved) or [edit this file directly](https://github.com/fulldecent/system-bus-radio/edit/master/TEST-DATA.tsv) and create a pull request.\n\n**WANTED:** Please post your test results using Raspberry Pi and other embedded systems. This may be particularly good targets because of less shielding/hardening of their hardware.\n\n**NEW:** See our [basic RTL SDR guide] to receive system bus signals using another computer with RTL SDR hardware.\n\n## Technical explanation\n\nThis program runs instructions on the computer that cause electromagnetic radiation. The emissions are of a broad frequency range. To be accepted by the radio, those frequencies must:\n\n* Be emitted by the computer processor and other subsystems\n* Escape the computer shielding\n* Pass through the air or other obstructions\n* Be accepted by the antenna\n* Be selected by the receiver\n\nBy trial and error, the above frequency was found to be ideal for that equipment.\n\nThe actual emissions are caused by the `_mm_stream_si128` instruction that writes through to a memory address. Inspiration for using this instruction was provided in:\n\n> Guri, M., Kachlon, A., Hasson, O., Kedma, G., Mirsky, Y. and Elovici, Y., 2015. GSMem: data exfiltration from air-gapped computers over GSM frequencies. In 24th USENIX Security Symposium (USENIX Security 15) (pp. 849-864).\n>\n> <https://www.usenix.org/node/190937>\n\nPlease note that replacing `_mm_stream_si128` with a simple `x++;` will work too. The author's experience has been that  `_mm_stream_si128` produces a stronger signal. There may be other ideas that work even better, and it would be nice to improve this method to be more portable (not require SSE extensions).\n\nThe program uses square wave modulation, which is depicted below:\n\n```\n|<--------------------TIME-------------------->|\n|                                              |\n|‾|_|‾|_|‾|_____________|‾|_|‾|_|‾|_____________\n|                       |   |   |\n|<------SIGNAL--------->|   |   |\n                            |   |\n                            |<->| CARRIER\n```\n\nNotes on high precision time APIs:\n\n* Get current time\n  * mach_absolute_time() gives time in int64_t of nanoseconds\n    * Converting to nanoseconds <https://developer.apple.com/library/mac/qa/qa1398/_index.html>\n    * Declared <https://opensource.apple.com/source/xnu/xnu-1456.1.26/osfmk/mach/mach_time.h>\n    * Definition <https://opensource.apple.com/source/Libc/Libc-320/i386/mach/mach_absolute_time.c>\n  * clock_get_time() gives a mach_timespec_t time\n    * Called from mach_absolute_time()\n  * mach_timespec_t\n    * Type documentation <https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/KernelProgramming/services/services.html>\n    * Declaration <https://opensource.apple.com/source/xnu/xnu-1456.1.26/osfmk/mach/clock_types.h>\n  * <http://stackoverflow.com/a/21352348/300224>\n  * <https://stackoverflow.com/questions/5167269/clock-gettime-alternative-in-mac-os-x>\n* Sleep\n  * mach_wait_until()\n    * Notes <https://developer.apple.com/library/ios/technotes/tn2169/_index.html>\n  * nanosleep()\n    * Apple doc <https://developer.apple.com/library/mac/documentation/Darwin/Reference/ManPages/man2/nanosleep.2.html>\n    * Definition <https://opensource.apple.com/source/Libc/Libc-320.1.3/gen/nanosleep.c?txt>\n  * clock_sleep_trap()\n    * Used from nanosleep()\n    * Declared <https://opensource.apple.com/source/xnu/xnu-1456.1.26/osfmk/mach/mach_traps.h>\n    * Definition <http://unix.superglobalmegacorp.com/xnu/newsrc/osfmk/kern/clock.c.html>\n    * Uses clock_sleep_internal()\n    * Uses ADD_MACH_TIMESPEC\n  * clock type constants <https://opensource.apple.com/source/xnu/xnu-1456.1.26/osfmk/mach/clock_types.h?txt>\n    * TIME_ABSOLUTE\n    * TIME_RELATIVE\n    * Defines ADD_MACH_TIMESPEC(t1, t2) // t1  += t2\n    * Defines CMP_MACH_TIMESPEC(t1, t2) // t1 <=> t2, also (t1 - t2) in nsec with max of +- 1 sec\n  * msleep() <https://developer.apple.com/library/mac/documentation/Darwin/Conceptual/KernelProgramming/services/services.html>\n    * time/timer.c /  <http://lxr.free-electrons.com/source/kernel/time/timer.c#L1673>\n  * kern/clock.h <https://opensource.apple.com/source/xnu/xnu-1456.1.26/osfmk/kern/clock.h>\n\n## Press coverage\n\n* <https://hardware.slashdot.org/story/16/03/01/1727226/microcasting-color-tv-by-abusing-a-wi-fi-chip>\n* <https://news.softpedia.com/news/emitting-radio-waves-from-a-computer-with-no-radio-transmitting-hardware-501260.shtml>\n* <https://tenwatts.blogspot.com/2018/01/system-bus-radio.html>"
  },
  {
    "path": "RTL-SDR-GUIDE.md",
    "content": "# RTL STR Basic Setup\n\n## Test subject\n\nFor this guide, we are inspecting a **MacBook Pro M1 (13-inch, 2020)** for electromagnetic radiation.\n\nThis subject is tough because:\n\n- Low power (wattage)\n- System is tightly integrated (no user-replaceable RAM)\n- Aluminum casing\n\nIf we are successful with the approaches in this paper, you should get even better results with other kinds of laptops.\n\n## Hardware\n\n*Other setups will work too. But this guide is explored with the following hardware:*\n\n* Computer running macOS (to run SDR)\n* RTL-SDR Blog V3 / [buy from manufacturer](https://www.rtl-sdr.com/buy-rtl-sdr-dvb-t-dongles/) / [data sheet](https://www.rtl-sdr.com/wp-content/uploads/2018/02/RTL-SDR-Blog-V3-Datasheet.pdf)\n* Ham It Up Plus / [buy from manufacturer](https://nooelec.com/store/ham-it-up-plus.html) / [data sheet](https://www.nooelec.com/store/downloads/dl/file/id/99/product/284/ham_it_up_plus_datasheet_revision_2.pdf)\n  * Requires a USB A/B cable and a USB power source (do not use the same device running your SDR)\n* Balun One Nine / [buy from manufacturer](https://www.nooelec.com/store/balun-one-nine-v2.html) / could not find data sheet\n* An AM loop antenna\n* Affiliate link to buy everything / [from Amazon](https://www.amazon.com/ideas/amzn1.account.AHUITP6B2VTROJ7IMNP2LCUA5QDA/18H46X17FDG76?type=explore&_encoding=UTF8&tag=phornetandrel-20&linkCode=ur2&linkId=dedd255a129c5ac7415a9dcb713ae618&camp=1789&creative=9325)\n\nYou may have seen that the RTL-SDR Blog V3 above [already includes support for lower frequencies](https://www.rtl-sdr.com/rtl-sdr-blog-v-3-dongles-user-guide/), obviating the upconverter (Ham It Up Plus). General advice on the scene has been that you want to use an upconverter rather than the built-in bias tee. If I can get better results with the bias tee approach I will update this guide to recommend that simpler and more ecoromical approach.\n\n## Setup\n\nYour equipment is plugged in as:\n\n```\nComputer --M/M-plug-- RTL-SDR --M/M-plug-- Ham It Up Plus --optional-long-wire-- Balun One Nine --spring-terminal-- antenna\n```\n\n![RF test setup](https://user-images.githubusercontent.com/382183/115773678-98d16a80-a37e-11eb-9f4b-4dabb9d401a2.jpg)\n\nNote that the upconverter means you will be tuning your radio to +125 MHz offset versus the frequencies you want.\n\n## Software\n\n**Test subject**\n\nFor quick browsing and playing, I used the [counter and threads](https://github.com/fulldecent/system-bus-radio/tree/master/Using%20counter%20and%20threads) implementation running on the M1 test subject. This allowed me to quickly edit the code and rerun it at different frequencies. (I ran the compiler command by hand as the Makefile did not work on the M1. Not sure if this needs fixing.)\n\n**Radio**\n\nThis was an 2018 MacBook Pro (Intel). [CubicSDR](https://cubicsdr.com) was easy to set up. Also it claimed to let me try the bias tee approach, but I failed to make it work.\n\nAlternately, I tried running [RTL Power](http://kmkeen.com/rtl-power/) to sweep various frequencies. Try it like this with the test subject off:\n\n```sh\ntime rtl_power -f 125M:126M:20K -g 50 -i 1m -1 noise.csv; say done\n```\n\nAnd then run it again with `signal.csv`. And compare those two results.\n\nHere is a quick Swift program to convert from RTL power into something you can use in Excel:\n\n```swift\nimport Foundation\nwhile let line = readLine() {\n    //\tdate, time, Hz low, Hz high, Hz step, samples, dbm, dbm, ...\n    let columns = line.components(separatedBy: \", \")\n\n    let hzLow = Double(columns[2])!\n    let hzStep = Double(columns[4])!\n    var hzCurrent = hzLow\n    for dbm in columns[6...] {\n        print(Int(hzCurrent), dbm)\n        hzCurrent += hzStep\n    }\n}\n```\n\nAnd of course:\n\n```sh\npaste -d, noise-transpose.csv signal-transpose.csv > merged.csv\n```\n\nNow you can plot the signal-to-noise ratio.\n\n## Results\n\nThe antenna placed directly over the bottom left of the keyboard produces the best signal. Start here so you can clearly hear the signal and as your tuning improves, begin backing away the antenna.\n\nI could clearly hear the signal between 63 kHz and 5.5 MHz using bandwidths between 10 kHz and 50 kHz. The best signal was 1.52 MHz at 40 kHz bandwidth. Using the AM demodulator.\n\nWith this approach I achieved audible signals up to only several inches away from the M1 Mac. Not very useful for spying/exfiltration. Possible ways to improve that are below.\n\n## Further research\n\n*These are a little more advanced idea I'd like to try with a mentor. If you'd like to pick up the ball and explain how to improve with these techniques, please feel free to open a new wiki page, and ping @fulldecent in a new issue.*\n\n* It may be possible to improve the signal by adding a low-noise amplifier between the antenna and the upconverter. I'd like to test the signal levels around these compenents and read specs before considering that further.\n* I could not get GNU Radio to connect to the RTL-SDR V3. It could certainly create a better custom receiver for this project:\n  1. Wideband 2.5MHz (or, somehow, twin 5.0MHz) signal input\n  2. A 1,000-band equalizer based on the envelope above (see RTL Power above)\n  3. Demodulate with AM\n  4. Tight bandpass at 440 Hz (for a 440 Hz system bus signal)\n* Better hardware. If you can recommend a better RTL-SDR, antenna, LNA, upconverter that might help, please let me know and I can add to my [project tip jar](https://github.com/fulldecent/system-bus-radio#system-bus-radio)."
  },
  {
    "path": "TEST-DATA.tsv",
    "content": "Date\tTester\tTransmitter\tReceiver\tSoftware\tFrequency\tResult\tRecording\r\n2016-03-01\tWilliam Entriken\tMacBook Air (13-inch, Early 2015)\tSony STR-K670P, stock antenna\t_mm_stream_si128\t1580 kHz\t2m thru air, 1m thru drywall\thttps://youtu.be/caGPmyMLYUI\r\n2016-03-01\tScott Buchanan\t\"MacBook Pro Retina 15\"\", early 2013\"\tN/A\t_mm_stream_si128\t\t?m audible\thttps://goo.gl/ll3PxH\r\n2016-03-01\tSamuel Steele\tMacBook Air (13-inch, Mid-2013)\tOnkyo HT-R550, JVC Loop antenna\t_mm_stream_si128\t1580 kHz\t\"2\"\", noisy by 6\"\t \r\n2016-03-01\tChris Smolinski\tMBP (??-inch, 2010)\tnetSDR, ??? antenna\t_mm_stream_si128\tEntire AM band\tNo signal found\t \r\n2016-03-01\tChris Smolinski\tiMac (??-inch, 2015)\tnetSDR, ??? antenna\t_mm_stream_si128\tEntire AM band\tNo signal found\t \r\n2016-03-01\tChris Smolinski\tMBP (??-inch, 2010)\tSony 7600G, no antenna\t_mm_stream_si128\t1580 kHz, Long wave\t4\thttps://youtu.be/l8AYHnF8ZrA\r\n2016-03-01\tChris\tHP ENVY 15-j142na (i7 version), Linux\tIcom IC-R10, ??? antenna\tPR #19\t\t?m audible\thttps://youtu.be/TXkh1ANSFGw\r\n2016-03-01\tJoão Ventura\tMacBook Pro (15-inch, Late 2013)\tTech Fuzzion, tele antenna\t_mm_stream_si128\t1600 kHz\t4\thttps://youtu.be/oXAeGZaka7o\r\n2016-03-01\tElvis Pfutzenreuter\tMacBook (12-inch, Early 2015)\tSony ICF-SW11, internal antenna\t_mm_stream_si128\t1580 kHz\t2m, recommends turning off mains & light\t \r\n2016-03-01\tsomini\tAsus X201E, Linux\tClock radio, internal antenna\t_mm_stream_si128\t1580 kHz\t4\thttps://youtu.be/Nroc2BtO6NU\r\n2016-03-01\tjanka102\tMacBook Pro (15-inch, Early 2011)\tiHome iP90, included AM antenna\t_mm_stream_si128\t1580 kHz\t8\thttps://youtu.be/qN9D3bxkbXk\r\n2016-03-01\tRyan Faerman\tMacBook Air (11-inch, 2014)\tGrundid Traveler 2 Digital, internal antenna\t_mm_stream_si128\t1600 kHz\t\"6\"\"-8\"\t \r\n2016-03-02\tTomi Salmi\tMac mini (Late 2014)\tSharp stereo cassette recorder WQ-T282H(GR), tele antenna\t_mm_stream_si128\t1580 kHz\t\"4\"\" noisy\"\t \r\n2016-03-02\tFe Yi\tMacBook Pro (13-inch, Early 2015)\tTECSUN PL-310ET, internal antenna\t_mm_stream_si128\t1580 kHz\t~10cm above keyboard\t \r\n2016-03-02\tRyou Ezoe\tAcer ASPIRE 5750, GNU/Linux\tTecsun PL-310 fm/am Stereo World Band Dsp Receiver, internal antenna\tPR #12\t1440 kHz\t30cm\t \r\n2016-03-02\tYuval Adam\tMacBook Pro (13-inch, Mid-2010)\tHackRF, 125Mhz upconverter, random wire antenna\t_mm_stream_si128\t1580 kHz\tNo discernible signal\t \r\n2016-03-02\tKyohei Takahashi\tMacBook Pro (Retina, 13-inch, Late 2012)\tKOIZUMI SAD-7701-R AM mode\t_mm_stream_si128\t500 kHz – 1400 kHz\t30cm\thttps://youtu.be/RJlOnoK5WpQ\r\n2016-03-02\tDavid Haberthür\t\"MacBook Pro Core i7 2.4 15\"\" Late 2011\"\tSony CFD-S38L\t_mm_stream_si128\t1584 kHz\t6cm\t \r\n2016-03-02\tJeremy Zerfas\tMacBook Pro (15-inch, Mid 2012) 2.3\tSony CFS-201 boom-box, internal antenna\t_mm_stream_si128\tVarious AM channels\t6'\t \r\n2016-03-02\tJeremy Zerfas\tMacBook Pro (15-inch, Early 2008) 2.4\tYamaha RX-V675, Loop antenna\tnanosleep mod\tVarious AM channels\t7', definitely farther than Mid 2012 model w/ same rcvr\t \r\n2016-03-02\tJeremy Zerfas\tAthlon II X2 240, Gigabyte GA-MA785GM-US2H, Antec FusionRemote 350\tYamaha RX-V675, Loop antenna\tPR #12\tVarious AM channels\t\"6\"\" from the processor\"\t \r\n2016-03-02\tNipun Gunawardena\tMacBook Pro Retina (13-inch, Late 2013)\tOnkyo CR305TX, Loop antenna\t_mm_stream_si128\t1610 kHz\t85cm\tOther frequencies also usable when very close\r\n2016-03-02\tMasahiko Uota\tMacBook Pro 2.8GHz i7 15-inch Mid 2014\tSony ICF-T46, no antenna\t_mm_stream_si128\t1300 kHz, 900 kHz\t6 inches\thttps://twitter.com/muota_here/status/704924596802342913\r\n2016-03-02\tYuji Fujita\tThinkpad X200\tSony ICF-SW100\tPR #12\t1363 kHz\t0.5m\thttps://youtu.be/li9hHM4NkWA\r\n2017-07-25\tErnesto Sanchez\tLenovo Thinkpad X201\tINDIN BC-R28\tmaster\t1600 kHz\t0.3m\tNone\r\n2016-03-02\tRedgar Nord\tHP ProBook 4340s, Linux\tSharp radio, whip antenna\tPR #12\t~1590 kHz\t\"6-7\"\", very orientation dependent\"\t \r\n2016-03-02\tRedgar Nord\tRaspberryPi, Linux\tSharp radio, whip antenna\tPR #12\t~1590 kHz\tNo signal at all\t \r\n2016-03-02\tErin Pinheiro\tAcer Aspire E1-572-6 BR691\tGeneric AM radio, retractable antenna\tPR #12\t~1590 kHz\t10-20cm Slightly noisy\thttps://dl.dropboxusercontent.com/u/9435923/code/audio_2016-03-02_18-24-30.ogg\r\n2016-03-02\tTrevor Summerfield\tMacBook Pro (Retina, 15-inch, Mid 2015)\tGrundig G8 Traveler II Digital, Internal AM Antenna\t_mm_stream_si128\t1580 kHz\t8” above keyboard 50dbu signal, 0db SNR, audible\t \r\n2016-03-05\tAlessio Gerace\tMacBookPro Retina (13-inch, 2014)\tMajestic TT34CD/TP/USB\t_mm_stream_si128\t~1610 kHz\t10/30 cm\t \r\n2016-03-05\tQuan Yang\tMacBookPro Retina (Late 2013)\tAR1741 Multiband Receiver\tJavascript\t1560 khz\t0.5m, more frequencies usable if charging\thttps://youtu.be/6tM4EKUYogI\r\n2016-03-07\tMehdi Asgari\tMacBook Pro Retina (13-inch, 2015)\tTecsun PL680, internal whip antenna\t_mm_stream_si128\t1610 kHz\t90cm\t \r\n2016-03-10\tGabriel Tremblay\tCooler Master HAF 912, ASUS M5A97, AMD FX-8120, Corsair Vengeance 32GB\tNot known\tPR #12\t~1550 kHz\t~20-30cm\t \r\n2016-09-05\tChris Rochford\tMacbook Pro Retina (13-inch, 2015)\tYamaha HTR-5730, loop antenna\tJavascript\t1580 kHz\t6 inches\t \r\n2016-10-13\tJohn Sampson\tMacBook Pro Retina (15-inch, Mid 2014)\tSony ICFCS15IPN (stock antenna)\t_mm_stream_si128\t~1560 kHz\t5 inches\t \r\n2018-01-24\tPhilipp Hanslovsky\tLenovo X1 Carbon (14-inch, 2014)\tYamaha RX-V675, Loop antenna\t_mm_stream_si128 @ 03c3689\t1590 kHz, 1600kHz\t5 inches\t \r\n2018-01-24\tFabian Briese\tDell Inspiron 17 7000 (7737)\tTevion Compact Disk Player\tWebbrowser (Chrome)\t1560 kHz\t125-150cm\t \r\n2018-01-30\tTroy Giorshev\tMacBook Air (13-inch, Early 2014)\tEton Mini 300\t_mm_stream_si128\t1535 kHz\t30cm\t \r\n2020-12-17\tSteven Shim\tMacBook Pro M1 (13-inch, 2020)\tSony AM/FM Clock Radio\t_mm_stream_si128\t1560kHz\t2-3 inches from bottom right of trackpad\t\r\n2022-02-01\tIlya Semichastnov\tMacBook Pro (16-inch, 2019)\tYaesu FT-817\t_mm_stream_si128\t1530kHz\twire antenna 50cm below the laptop\t\r\n2024-01-03\tChristian Mürtz\tMacBook Pro Late 2016\tNORDMENDE - Compact Recorder 5043 K\t_mm_stream_si128\t1550 kHz\t~30cm\thttps://www.youtube.com/watch?v=jUKchOM_TjM\r\n2024-07-09  Noah King MacBook Late 2007  1958 Orion 10 Transistor Radio  _mm_stream_si128  ~1690 kHz No antenna 10cm-1m  https://www.youtube.com/watch?v=YCXruZPXfss  \r\n"
  },
  {
    "path": "docs/.gitignore",
    "content": "/_site\nGemfile.lock\n"
  },
  {
    "path": "docs/Gemfile",
    "content": "source 'https://rubygems.org'\ngem 'github-pages', group: :jekyll_plugins\n"
  },
  {
    "path": "docs/airgap.js",
    "content": "var player; // Define \"player\" var to make my code linter happy\n\nfunction start() { // Start Web Worker & send song data to player\n\tvar logs = document.getElementById('progress'); // Define log element\n\twindow.logs = logs; // Make variable Global\n\twindow.logs.value = \"\";\n\n\t// Create Web Worker if it doesn't already exist\n\tif (window.Worker && typeof(player) == \"undefined\") {\n\t\tvar player = new Worker(\"worker.js\");\n\t\twindow.player = player; // Make variable Global\n\t\tplayer.onmessage = function(event) {\n\t\t\tvar data = event.data;\n\t\t\tconsole.log(data)\n\t\t\twindow.logs.value += \"x\\n\";\n\t\t};\n\n\t\t// Send song data to player\n\t\tvar song = document.getElementById(\"tune\").value;\n\t\tplayer.postMessage(song);\n\t}\n}\n\nfunction end() { // Stops the Web Worker\n\twindow.logs.value = \"\";\n\tplayer.terminate();\n}\n"
  },
  {
    "path": "docs/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n  <meta name=\"description\" content=\"System Bus Radio JS Port: Play AM radio waves from a computer without an AM radio, using only your web browser.\">\n  <title>System Bus Radio</title>\n  <link rel=\"stylesheet\" href=\"main.css\"></style>\n  <script src=\"airgap.js\"></script>\n</head>\n<body style=\"text-align:center\">\n  <header>\n    <h1>System Bus Radio</h1>\n  </header>\n  <p><em>Tested on MacBook Air / Chrome with AM tuner at 1560 kHz. <a href=\"https://github.com/fulldecent/system-bus-radio/blob/master/TEST-DATA.tsv\">See field reports</a> for other equipment and frequencies.</em></p>\n  <p>\n    <button id=\"play\" onclick=\"start()\">Play tune</button>\n    <button id=\"stop\" onclick=\"end()\">Stop tune</button>\n  </p>\n  <section style=\"display:flex;width:60%;margin:auto\">\n    <textarea style=\"width:2rem\" id=\"progress\" readonly></textarea>\n    <textarea style=\"flex:1;height:50rem\" id=\"tune\">\n2673 400\n2349 400\n2093 400\n2349 400\n2673 400\n0 400\n2673 400\n0 400\n2673 790\n2349 400\n2349 400\n0 400\n2349 790\n2673 400\n3136 400\n0 400\n3136 790\n2673 400\n2349 400\n2093 400\n2349 400\n2673 400\n0 400\n2673 400\n0 400\n2673 400\n0 400\n2673 400\n2349 400\n0 400\n2349 400\n2673 400\n2349 400\n2093 790</textarea>\n  </section>\n  <p>Edit the above to make any music you like. <a href=\"https://github.com/fulldecent/system-bus-radio/issues/28\">Tune file format</a> is frequency (Hz) and time (ms).</p>\n  <p>Chrome has errors if you open this file locally (<code>file://</code>). Try using <code>php -S localhost:8000</code> or similar for a quick web server.</p>\n  <p>Ported by <a href=\"https://github.com/quanyang\">Yeo Quan Yang</a> & maintained by <a href=\"https://github.com/rocketinventor\">Elliot Gerchak</a>.</p>\n  <p>Original machine code by <a href=\"https://github.com/fulldecent\">William Entriken</a>.</p>\n  <p><strong>Project site at <a href=\"https://github.com/fulldecent/system-bus-radio\">https://github.com/fulldecent/system-bus-radio</a></strong></p>\n</body>\n</html>\n"
  },
  {
    "path": "docs/main.css",
    "content": "/* Main style */\nbody {\n  background-color: #3498db;\n  color: white;\n  font-family: Futura, Helvetica, \"Century Gothic\";\n  text-shadow: 1px 1px 0px black;\n}\np {\n  font-size: 1.2rem;\n}\n\na {\n  color: white;\n}\na:hover {\n  color: red;\n}\nh1 {\n  font-size: 3rem;\n  font-weight: normal;\n  text-align: center;\n}\n\ntextarea {\n  font-size: 1.25rem;\n  font-family: courier;\n}\n\n/* BUTTON FROM https://github.com/eisenfox/css3-button-hovers */\nbutton {\n  background: purple;\n  text-align: center;\n  display: inline-block;\n  position: relative;\n  text-decoration: none;\n  color: #fff;\n  text-transform: capitalize;\n  font-size: 18px;\n  padding: 20px 0px;\n  width: 150px;\n  border-radius: 6px;\n  overflow: hidden;\n  -webkit-transition: all 0.2s linear 0s;\n  transition: all 0.2s linear 0s;\n}\nbutton#play:before {\n  content: \"\\25BA\";\n}\nbutton#stop:before {\n  content: \"\\25FC\";\n}\nbutton:before {\n  display: -webkit-box;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n      -ms-flex-align: center;\n          align-items: center;\n  -webkit-box-pack: center;\n      -ms-flex-pack: center;\n          justify-content: center;\n  position: absolute;\n  top: 0;\n  left: 0px;\n  height: 100%;\n  width: 30px;\n  background-color: rgba(255, 255, 255, 0.3);\n  border-radius: 0 50% 50% 0;\n  -webkit-transform: scale(0, 1);\n          transform: scale(0, 1);\n  -webkit-transform-origin: left center;\n          transform-origin: left center;\n  -webkit-transition: all 0.2s linear 0s;\n  transition: all 0.2s linear 0s;\n}\nbutton:hover {\n  text-indent: 30px;\n}\nbutton:hover:before {\n  -webkit-transform: scale(1, 1);\n          transform: scale(1, 1);\n  text-indent: 0;\n}\n"
  },
  {
    "path": "docs/worker.js",
    "content": "//Credits to https://github.com/fulldecent/system-bus-radio\n//As well as Jordan Harband for the nodejs simd library\n//Tested to be working on Chrome at 1560khz\n\nvar window = self; // Create \"window\" object\n\nfunction now() {\n\treturn window.performance.now() * 1000000;\n}\n\nvar NSEC_PER_SEC = 1000000000;\nvar register = 3.1415;\n\nfunction square_am_signal(freq, time) { // This function generates the radio waves\n\tpostMessage(\"\\nPlaying / \" + time + \" seconds / \" + freq + \"Hz\");\n\tvar period = NSEC_PER_SEC / freq;\n\tvar start = now();\n\tvar end = now() + time * NSEC_PER_SEC;\n\twhile (now() < end) {\n\t\tvar mid = start + period / 2;\n\t\tvar reset = start + period;\n\t\twhile (now() < mid) {\n\t\t\tfor (var i = 0; i < 100; i++) {\n\t\t\t\tregister = 1 - Math.log(register) / 1.7193;\n\t\t\t}\n\t\t}\n\t\twhile (now() < reset) {}\n\t\tstart = reset;\n\t}\n}\n\nfunction play(song) { // Parse song data, and call on required scripts to run it\n\tsong = song.split(\"\\n\");\n\tvar length = song.length;\n\tvar line, freq, time;\n\tfor (var i = 0; i < length; i++) {\n\t\tline = song[i].split(\" \");\n\t\tif (+line[1] == 0) {\n\t\t\tpause(line[0]);\n\t\t}\n\t\telse {\n\t\t\ttime = (+line[1])*.001;\n\t\t\tfreq = +line[0];\n\t\t\tsquare_am_signal(freq, time);\n\t\t}\n\t}\n\n\tclose(); // Close Web Worker\n}\n\nfunction pause(time) { // A useless function to run when there is no noise to play\n\tpostMessage(\"\\nPaused / \" + time*.001 + \" seconds\");\n\tvar dt = new Date();\n\twhile ((new Date()) - dt <= time) { /* Do nothing */ }\n}\n\nonmessage = function(event) { // Recieve song data from main page\n\tvar data = event.data;\n\tplay(data);\n};"
  },
  {
    "path": "implementations/c-_mm_stream_si128/Makefile",
    "content": "CPPFLAGS=-Wall -O2 -msse2\n\nmain: main.c\n\n.PHONY: clean\n\nclean:\n\trm -f main\n"
  },
  {
    "path": "implementations/c-_mm_stream_si128/main.c",
    "content": "// SYSTEM BUS RADIO\n// https://github.com/fulldecent/system-bus-radio\n// Copyright 2016 William Entriken\n\n#include <stdio.h>\n#include <emmintrin.h>\n#include <inttypes.h>\n#include <time.h>\n#include <math.h>\n#ifdef __MACH__\n#include <mach/mach_traps.h>\n#include <mach/mach_time.h>\n#endif\n\n#ifndef NSEC_PER_SEC\n#define NSEC_PER_SEC 1000000000ull\n#endif\n\n#ifndef __MACH__\n#define TIME_ABSOLUTE CLOCK_REALTIME\ntypedef struct timespec mach_timespec_t;\ntypedef unsigned int mach_port_t;\n\nstatic inline uint64_t mach_absolute_time(void) {\n    mach_timespec_t tp;\n    int res = clock_gettime(CLOCK_REALTIME, &tp);\n    if (res < 0) {\n        perror(\"clock_gettime\");\n        exit(1);\n    }\n    uint64_t result = tp.tv_sec * NSEC_PER_SEC;\n    result += tp.tv_nsec;\n    return result;\n}\n\n// non-conformant wrapper just for the purposes of this application\nstatic inline void clock_sleep_trap(mach_port_t clock_port, int sleep_type, time_t sec, long nsec, mach_timespec_t *remain) {\n    mach_timespec_t req = { sec, nsec };\n    int res = clock_nanosleep(sleep_type, TIMER_ABSTIME, &req, remain);\n    if (res < 0) {\n        perror(\"clock_nanosleep\");\n        exit(1);\n    }\n}\n#endif // __MACH__\n\n__m128i reg;\n__m128i reg_zero;\n__m128i reg_one;\nmach_port_t clock_port;\nmach_timespec_t remain;\n\nstatic inline void square_am_signal(float frequency, float time) {\n    printf(\"Playing / %0.3f seconds / %4.0f Hz\\n\", time, frequency);\n    uint64_t period = NSEC_PER_SEC / frequency;\n\n    uint64_t start = mach_absolute_time();\n    uint64_t end = start + (uint64_t)(time * NSEC_PER_SEC);\n\n    while (mach_absolute_time() < end) {\n        uint64_t mid = start + period / 2;\n        uint64_t reset = start + period;\n        while (mach_absolute_time() < mid) {\n            _mm_stream_si128(&reg, reg_one);\n            _mm_stream_si128(&reg, reg_zero);\n        }\n        clock_sleep_trap(clock_port, TIME_ABSOLUTE, reset / NSEC_PER_SEC, reset % NSEC_PER_SEC, &remain);\n        start = reset;\n    }\n}\n\nint main(int argc, char* argv[])\n{\n#ifdef __MACH__\n    mach_timebase_info_data_t theTimeBaseInfo;\n    mach_timebase_info(&theTimeBaseInfo);\n    puts(\"TESTING TIME BASE: the following should be 1 / 1\");\n    printf(\"  Mach base: %u / %u nanoseconds\\n\\n\", theTimeBaseInfo.numer, theTimeBaseInfo.denom);\n#endif\n\n    uint64_t start = mach_absolute_time();\n    uint64_t end = mach_absolute_time();\n    printf(\"TESTING TIME TO EXECUTE mach_absolute_time()\\n  Result: %\"PRIu64\" nanoseconds\\n\\n\", end - start);\n\n    reg_zero = _mm_set_epi32(0, 0, 0, 0);\n    reg_one = _mm_set_epi32(-1, -1, -1, -1);\n\n    FILE* fp;\n    if (argc == 2) {\n        fp = fopen(argv[1], \"r\");\n    } else {\n        printf(\"No song file given!\\nUsage: %s file.song\\n\", argv[0]);\n        exit(1);\n    }\n\n    char buffer[20] = {0};\n    int freq_hz;\n    int time_ms;\n    while (1) {\n        fgets(buffer, 20 - 1, fp);\n        if (sscanf(buffer, \"%d %d\", &freq_hz, &time_ms) == 2) {\n            square_am_signal(1.0 * time_ms / 1000, freq_hz);\n        }\n        if (feof(fp)) {\n            rewind(fp);\n        }\n    }\n}\n"
  },
  {
    "path": "implementations/c-apple-silicon/Makefile",
    "content": "CPPFLAGS=-Wall -O2\n\nmain: main.c\n\n.PHONY: clean\n\nclean:\n\trm -f main\n"
  },
  {
    "path": "implementations/c-apple-silicon/README.md",
    "content": "# System Bus Radio for Apple Silicon (M1/M2/M3)\n\nThis implementation is specifically optimized for Apple Silicon (M1/M2/M3) chips, which have strict power management and thermal throttling that can interfere with traditional memory bus noise generation.\n\n## Why a specialized version?\n\nStandard implementations that hammer the memory bus continuously are quickly detected by the macOS power management unit (PMU) as \"runaway processes.\" This results in:\n*   Thermal throttling (clock speed reduction).\n*   Process prioritization penalties (moving to efficiency cores).\n*   Signal loss after a few seconds of playback.\n\n## Solution: Pulse-Packet Modulation\n\nThis implementation uses a **\"Pulse-Packet\" strategy**:\n1.  **Burst Transmission:** It blasts the memory bus with 4 parallel threads (utilizing all P-Cores) for short durations (e.g., 20ms).\n2.  **Micro-Sleep:** It forces a tiny sleep (e.g., 0.5ms) between bursts.\n\n\nThis intermittent load tricks the PMU into thinking the process is behaving normally, allowing sustained high-power transmission without throttling.\n\n## Hardware Setup (Tested Configuration)\n\n*   **Computer:** MacBook Air (M1, 2020)\n*   **Radio:** SONY ICF-B99 (AM Receiver)\n*   **Frequency:** ~1100 kHz (1.1 MHz) - *Note: This differs from the original 1580 kHz recommendation for Intel Macs.*\n*   **Antenna Position:** Bottom center of the laptop (near the logic board).\n\n## Compilation & Usage\n\n1.  Compile the program:\n    ```sh\n    gcc -O3 main.c -o main\n    ```\n\n2.  Run with a tune file:\n    ```sh\n    ./main ../../tunes/mary_had_a_little_lamb.tune\n    ```\n\n3.  **Important:** For best results:\n    *   Keep your Mac plugged into power.\n    *   Close other heavy applications.\n    *   Tune your AM radio to **~1100 kHz** (explore nearby frequencies for the strongest signal).\n"
  },
  {
    "path": "implementations/c-apple-silicon/main.c",
    "content": "// SYSTEM BUS RADIO - M1 PULSE-PACKET TRANSMITTER (4 CORES)\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <arm_neon.h>\n#include <time.h>\n#include <mach/mach_time.h>\n#include <mach/thread_policy.h>\n#include <mach/mach_init.h>\n#include <mach/thread_act.h>\n#include <pthread.h>\n#include <pthread/qos.h>\n\n#define NUM_THREADS 4\n#define MEM_SIZE (64 * 1024 * 1024)\n#define PACKET_MS 20 \n\nuint8_t *g_mem;\nstatic mach_timebase_info_data_t timebase_info;\nvolatile int g_running = 1;\n\n// Shared Parameters\nvolatile uint64_t g_current_freq = 0;\nvolatile uint64_t g_current_duration_ms = 0;\n\n// Custom Barrier Structure\ntypedef struct {\n    pthread_mutex_t mutex;\n    pthread_cond_t cond;\n    int count;\n    int crossing;\n    int total;\n} my_barrier_t;\n\nmy_barrier_t g_barrier_start;\nmy_barrier_t g_barrier_end;\n\nvoid barrier_init(my_barrier_t *b, int total) {\n    pthread_mutex_init(&b->mutex, NULL);\n    pthread_cond_init(&b->cond, NULL);\n    b->count = 0;\n    b->crossing = 0;\n    b->total = total;\n}\n\nvoid barrier_wait(my_barrier_t *b) {\n    pthread_mutex_lock(&b->mutex);\n    b->count++;\n    if (b->count >= b->total) {\n        b->crossing++;\n        b->count = 0;\n        pthread_cond_broadcast(&b->cond);\n    } else {\n        int my_crossing = b->crossing;\n        while (my_crossing == b->crossing) {\n            pthread_cond_wait(&b->cond, &b->mutex);\n        }\n    }\n    pthread_mutex_unlock(&b->mutex);\n}\n\nstatic inline void bus_poke(size_t offset) {\n    // Write 0xFFFFFFFFFFFFFFFF to the bus\n    uint64_t val = 0xFFFFFFFFFFFFFFFFull;\n    asm volatile (\n        \"stnp %0, %0, [%1]\\n\" \"stnp %0, %0, [%1, #16]\\n\"\n        \"stnp %0, %0, [%1, #32]\\n\" \"stnp %0, %0, [%1, #48]\\n\"\n        : : \"r\"(val), \"r\"(&g_mem[offset]) : \"memory\"\n    );\n}\n\nvoid set_realtime(int affinity_tag) {\n    pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);\n    thread_affinity_policy_data_t affinity;\n    affinity.affinity_tag = affinity_tag;\n    thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY, (thread_policy_t)&affinity, THREAD_AFFINITY_POLICY_COUNT);\n}\n\nvoid* worker_thread(void* arg) {\n    int id = (int)(size_t)arg;\n    set_realtime(id + 1);\n\n    size_t segment = MEM_SIZE / NUM_THREADS;\n    size_t offset = id * segment;\n    size_t end_offset = offset + segment - 64;\n    size_t current = offset;\n\n    while (g_running) {\n        // 1. Wait for start signal\n        barrier_wait(&g_barrier_start);\n        if (!g_running) break;\n\n        uint64_t freq = g_current_freq;\n        uint64_t duration = g_current_duration_ms;\n\n        if (freq > 0) {\n            uint64_t start = mach_absolute_time();\n            uint64_t end = start + (duration * 1000000ull * timebase_info.denom / timebase_info.numer);\n            uint64_t period = (1000000000ull / freq) * timebase_info.denom / timebase_info.numer;\n            uint64_t half_period = period / 2;\n\n            uint64_t now = start;\n            while (now < end) {\n                uint64_t next_edge = now + half_period;\n                // ON Phase\n                while (mach_absolute_time() < next_edge) {\n                    bus_poke(current);\n                    current += 64;\n                    if (current >= end_offset) current = offset;\n                }\n                next_edge += half_period;\n                // OFF Phase\n                while (mach_absolute_time() < next_edge) {\n                    asm volatile(\"yield\");\n                }\n                now = next_edge;\n            }\n        }\n\n        // 2. Report completion\n        barrier_wait(&g_barrier_end);\n    }\n    return NULL;\n}\n\nvoid play_tone(uint64_t freq_hz, uint64_t time_ms) {\n    uint64_t remaining = time_ms;\n    while (remaining > 0) {\n        uint64_t chunk = (remaining > PACKET_MS) ? PACKET_MS : remaining;\n        \n        g_current_freq = freq_hz;\n        g_current_duration_ms = chunk;\n        \n        // START\n        barrier_wait(&g_barrier_start);\n        \n        // END WAIT\n        barrier_wait(&g_barrier_end);\n        \n        // Short pause (0.5ms) to reset throttling counters\n        struct timespec ts = {0, 500000};\n        nanosleep(&ts, NULL);\n        \n        remaining -= chunk;\n    }\n}\n\nint main(int argc, char* argv[]) {\n    mach_timebase_info(&timebase_info);\n    if (argc != 2) {\n        fprintf(stderr, \"Usage: %s file.tune\\n\", argv[0]);\n        return 1;\n    }\n    FILE* fp = fopen(argv[1], \"r\");\n    if (!fp) { perror(\"fopen\"); return 1; }\n\n    g_mem = malloc(MEM_SIZE);\n    memset(g_mem, 0, MEM_SIZE);\n\n    barrier_init(&g_barrier_start, NUM_THREADS + 1);\n    barrier_init(&g_barrier_end, NUM_THREADS + 1);\n\n    pthread_t threads[NUM_THREADS];\n    for (int i = 0; i < NUM_THREADS; i++) {\n        pthread_create(&threads[i], NULL, worker_thread, (void*)(size_t)i);\n    }\n\n    set_realtime(0);\n    printf(\"M1 Pulse-Packet Mode (Custom Barrier). Playing...\\n\");\n\n    char buffer[128];\n    int freq_hz, time_ms;\n    while (1) {\n        if (fgets(buffer, sizeof(buffer), fp) == NULL) {\n            if (feof(fp)) {\n                rewind(fp);\n                continue;\n            }\n            break;\n        }\n        if (buffer[0] == '\\n' || buffer[0] == '#' || buffer[0] == ' ') continue;\n\n        if (sscanf(buffer, \"%d %d\", &freq_hz, &time_ms) == 2) {\n            printf(\"\\r%4d Hz \", freq_hz);\n            fflush(stdout);\n            play_tone(freq_hz, time_ms);\n        }\n    }\n\n    g_running = 0;\n    barrier_wait(&g_barrier_start); // Release waiting threads\n    \n    for (int i = 0; i < NUM_THREADS; i++) pthread_join(threads[i], NULL);\n    \n    fclose(fp);\n    return 0;\n}\n"
  },
  {
    "path": "implementations/c-apple-silicon-wav/Makefile",
    "content": "CPPFLAGS=-Wall -O2\n\nmain: main.c\n\n.PHONY: clean\n\nclean:\n\trm -f main\n"
  },
  {
    "path": "implementations/c-apple-silicon-wav/README.md",
    "content": "# c-apple-silicon-wav\n\nThis is a **delta implementation** based on the Apple Silicon version in\n`implementations/c-apple-silicon`, extended to play arbitrary WAV files.\n\nFor hardware assumptions and receiver setup (such as AM radio placement),\nsee `implementations/c-apple-silicon/README.md`.\nThis README summarizes **only the differences**.\n\n## Changes from c-apple-silicon\n\n1. Input format\n- Changed input from `.tune` to WAV\n- Run format: `./main input.wav`\n\n2. WAV loader\n- Added `RIFF/WAVE` validation\n- Scans and reads `fmt ` / `data` chunks\n- Handles chunk padding (even-byte alignment)\n- Downmixes multi-channel audio to mono\n- Applies input sample rate to playback timing\n\n3. Audio preprocessing\n- 32-bit-safe peak normalization\n- Voice-oriented pre-EQ (HP/LP + presence)\n- Compression and loudness boost via AGC + limiter\n\n4. Modulation method\n- Replaced random-sampling-based density generation with a Sigma-Delta\n  (error-accumulation) method\n- Improves intelligibility by reducing fine-grained random on/off artifacts\n\n5. Packet handling\n- Keeps sample time continuity across packets\n- Sets default `PACKET_MS` to `100ms`\n- Adjustable at runtime via `SBR_PACKET_MS`\n\n\n## Supported WAV\n\n- `RIFF/WAVE`\n- PCM 16-bit (`audio_format=1`, `bits_per_sample=16`)\n- 1 channel or more (internally downmixed to mono)\n- Any sample rate (tracked by internal timing)\n\nNot supported:\n- float PCM\n- 24/32-bit PCM\n- WAV with compressed codecs\n\n## Build\n\n```bash\ncd /Users/cho45/tmp/system-bus-radio/implementations/c-apple-silicon-wav\nmake\n```\n\n## Run\n\n```bash\n./main input.wav\n```\n\nExamples:\n\n```bash\n./main hello.wav\n./main sweep.wav\n```\n\n## Main runtime parameters\n\n- `SBR_DENSITY_EXP`: Exponent of the modulation curve\n- `SBR_DENSITY_DEPTH`: Modulation depth\n- `SBR_AGC_TARGET`: Target envelope level for AGC\n- `SBR_AGC_MAKEUP`: Makeup gain after AGC\n- `SBR_AGC_MAX_GAIN`: Maximum AGC gain\n- `SBR_PACKET_MS`: Packet length (ms)\n\n## Tuning guidelines\n\n- Still too quiet: increase `SBR_AGC_TARGET`, `SBR_AGC_MAKEUP`,\n  and `SBR_AGC_MAX_GAIN`\n- Choppy output: increase `SBR_PACKET_MS` (for example `200` to `500`)\n- Distortion: decrease `SBR_AGC_MAKEUP` or `SBR_DENSITY_DEPTH`\n"
  },
  {
    "path": "implementations/c-apple-silicon-wav/gen_sweep.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <math.h>\n\n#define SAMPLE_RATE 24000\n#define DURATION_PER_TONE 2 \n#define NUM_TONES 5\n\nint frequencies[] = {500, 1000, 2000, 4000, 8000};\n\nint main() {\n    FILE *fp = fopen(\"sweep.wav\", \"wb\");\n    if (!fp) return 1;\n    \n    uint32_t total_samples = SAMPLE_RATE * DURATION_PER_TONE * NUM_TONES;\n    uint32_t data_len = total_samples * 2;\n    uint32_t riff_len = data_len + 36;\n    \n    fwrite(\"RIFF\", 1, 4, fp);\n    fwrite(&riff_len, 4, 1, fp);\n    fwrite(\"WAVE\", 1, 4, fp);\n    \n    fwrite(\"fmt \", 1, 4, fp);\n    uint32_t fmt_len = 16;\n    uint16_t audio_fmt = 1; \n    uint16_t num_channels = 1;\n    uint32_t sample_rate = SAMPLE_RATE;\n    uint32_t byte_rate = SAMPLE_RATE * 2;\n    uint16_t block_align = 2;\n    uint16_t bits_per_sample = 16;\n    \n    fwrite(&fmt_len, 4, 1, fp);\n    fwrite(&audio_fmt, 2, 1, fp);\n    fwrite(&num_channels, 2, 1, fp);\n    fwrite(&sample_rate, 4, 1, fp);\n    fwrite(&byte_rate, 4, 1, fp);\n    fwrite(&block_align, 2, 1, fp);\n    fwrite(&bits_per_sample, 2, 1, fp);\n    \n    fwrite(\"data\", 1, 4, fp);\n    fwrite(&data_len, 4, 1, fp);\n    \n    for (int t = 0; t < NUM_TONES; t++) {\n        int freq = frequencies[t];\n        printf(\"Generating %d Hz...\\n\", freq);\n        for (int i = 0; i < SAMPLE_RATE * DURATION_PER_TONE; i++) {\n            double time = (double)i / SAMPLE_RATE;\n            double v = sin(2.0 * M_PI * freq * time);\n            int16_t sample = (int16_t)(v * 32000.0);\n            fwrite(&sample, 2, 1, fp);\n        }\n    }\n    \n    fclose(fp);\n    printf(\"Generated sweep.wav (500, 1000, 2000, 4000, 8000 Hz)\\n\");\n    return 0;\n}\n"
  },
  {
    "path": "implementations/c-apple-silicon-wav/main.c",
    "content": "// SYSTEM BUS RADIO - M1 WAV PLAYER (STOCHASTIC DENSITY MODULATION)\n// Uses probability to modulate bus density, achieving high-resolution AM.\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <math.h>\n#include <arm_neon.h>\n#include <time.h>\n#include <mach/mach_time.h>\n#include <mach/thread_policy.h>\n#include <mach/mach_init.h>\n#include <mach/thread_act.h>\n#include <pthread.h>\n#include <pthread/qos.h>\n\n#define NUM_THREADS 4\n#define MEM_SIZE (64 * 1024 * 1024)\n#define SAMPLE_RATE 24000.0\n#define PACKET_MS 100\n\nuint8_t *g_mem;\nstatic mach_timebase_info_data_t timebase_info;\nvolatile int g_running = 1;\n\nint16_t *g_audio_data = NULL;\nsize_t g_audio_len = 0; \ndouble g_sample_rate = SAMPLE_RATE;\ndouble g_density_exp = 0.55;\ndouble g_density_depth = 0.44;\ndouble g_agc_target_env = 0.36;\ndouble g_agc_makeup = 2.6;\ndouble g_agc_max_gain = 96.0;\ndouble g_packet_ms = PACKET_MS;\n\n// Synchronization\nvolatile size_t g_packet_start_sample = 0;\nvolatile size_t g_packet_num_samples = 0;\n\n// Custom Barrier\ntypedef struct {\n    pthread_mutex_t mutex;\n    pthread_cond_t cond;\n    int count;\n    int crossing;\n    int total;\n} my_barrier_t;\n\nmy_barrier_t g_barrier_start;\nmy_barrier_t g_barrier_end;\n\nvoid barrier_init(my_barrier_t *b, int total) {\n    pthread_mutex_init(&b->mutex, NULL);\n    pthread_cond_init(&b->cond, NULL);\n    b->count = 0;\n    b->crossing = 0;\n    b->total = total;\n}\n\nvoid barrier_wait(my_barrier_t *b) {\n    pthread_mutex_lock(&b->mutex);\n    b->count++;\n    if (b->count >= b->total) {\n        b->crossing++;\n        b->count = 0;\n        pthread_cond_broadcast(&b->cond);\n    } else {\n        int my_crossing = b->crossing;\n        while (my_crossing == b->crossing) {\n            pthread_cond_wait(&b->cond, &b->mutex);\n        }\n    }\n    pthread_mutex_unlock(&b->mutex);\n}\n\nstatic inline void bus_poke(size_t offset) {\n    uint64_t val = 0xFFFFFFFFFFFFFFFFull;\n    asm volatile (\n        \"stnp %0, %0, [%1]\\n\" \"stnp %0, %0, [%1, #16]\\n\"\n        \"stnp %0, %0, [%1, #32]\\n\" \"stnp %0, %0, [%1, #48]\\n\"\n        : : \"r\"(val), \"r\"(&g_mem[offset]) : \"memory\"\n    );\n}\n\nvoid set_realtime(int affinity_tag) {\n    pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);\n    thread_affinity_policy_data_t affinity;\n    affinity.affinity_tag = affinity_tag;\n    thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY, (thread_policy_t)&affinity, THREAD_AFFINITY_POLICY_COUNT);\n}\n\nvoid* worker_thread(void* arg) {\n    int id = (int)(size_t)arg;\n    set_realtime(id + 1);\n\n    size_t segment = MEM_SIZE / NUM_THREADS;\n    size_t offset = id * segment;\n    size_t end_offset = offset + segment - 64;\n    size_t current = offset;\n    \n    // Per-thread sigma-delta state. Stagger the phase across threads.\n    double pdm_error = (double)id / (double)NUM_THREADS;\n\n    // Timebase conversion constants\n    uint64_t numer = timebase_info.numer;\n    uint64_t denom = timebase_info.denom;\n    \n    // 1サンプルの長さ (mach time)\n    uint64_t sample_duration_mach = (uint64_t)((1000000000.0 / g_sample_rate) * (double)denom / (double)numer);\n\n    uint64_t next_sample_time = mach_absolute_time();\n\n    while (1) {\n        barrier_wait(&g_barrier_start);\n        if (!g_running) break;\n\n        size_t start_idx = g_packet_start_sample;\n        size_t num_samples = g_packet_num_samples;\n        uint64_t now = mach_absolute_time();\n        if (next_sample_time < now) next_sample_time = now;\n\n        for (size_t i = 0; i < num_samples; i++) {\n            int16_t sample = g_audio_data[start_idx + i];\n            \n            // Strong speech-oriented companding to make low-level voice audible.\n            double sample_norm = (double)sample / 32768.0; // -1.0 ~ +1.0\n            double shaped = copysign(pow(fabs(sample_norm), g_density_exp), sample_norm);\n            double density = 0.5 + (shaped * g_density_depth);\n            if (density < 0.01) density = 0.01;\n            if (density > 0.99) density = 0.99;\n            next_sample_time += sample_duration_mach;\n            \n            // 次のサンプル時刻まで sigma-delta 的に密度を実現する\n            while (mach_absolute_time() < next_sample_time) {\n                pdm_error += density;\n                if (pdm_error >= 1.0) {\n                    bus_poke(current);\n                    current += 64;\n                    if (current >= end_offset) current = offset;\n                    pdm_error -= 1.0;\n                } else {\n                    asm volatile(\"yield\");\n                }\n            }\n        }\n\n        barrier_wait(&g_barrier_end);\n    }\n    return NULL;\n}\n\nstatic uint16_t read_le16(const uint8_t* p) {\n    return (uint16_t)p[0] | ((uint16_t)p[1] << 8);\n}\n\nstatic uint32_t read_le32(const uint8_t* p) {\n    return (uint32_t)p[0] |\n           ((uint32_t)p[1] << 8) |\n           ((uint32_t)p[2] << 16) |\n           ((uint32_t)p[3] << 24);\n}\n\nstatic double get_env_double(const char* name, double default_value) {\n    const char* v = getenv(name);\n    if (!v || !*v) return default_value;\n    char* end = NULL;\n    double parsed = strtod(v, &end);\n    if (end == v) return default_value;\n    return parsed;\n}\n\nint load_wav(const char* filename) {\n    FILE* fp = fopen(filename, \"rb\");\n    if (!fp) { perror(\"fopen\"); return 0; }\n\n    uint8_t riff_header[12];\n    if (fread(riff_header, 1, sizeof(riff_header), fp) != sizeof(riff_header)) {\n        fprintf(stderr, \"Invalid WAV: header too short\\n\");\n        fclose(fp);\n        return 0;\n    }\n    if (memcmp(riff_header, \"RIFF\", 4) != 0 || memcmp(riff_header + 8, \"WAVE\", 4) != 0) {\n        fprintf(stderr, \"Invalid WAV: missing RIFF/WAVE\\n\");\n        fclose(fp);\n        return 0;\n    }\n\n    int have_fmt = 0;\n    int have_data = 0;\n    uint16_t fmt_audio_format = 0;\n    uint16_t fmt_channels = 0;\n    uint32_t fmt_sample_rate = 0;\n    uint16_t fmt_bits_per_sample = 0;\n    long data_offset = 0;\n    uint32_t data_size = 0;\n\n    while (1) {\n        uint8_t chunk_header[8];\n        if (fread(chunk_header, 1, sizeof(chunk_header), fp) != sizeof(chunk_header)) break;\n\n        uint32_t chunk_size = read_le32(chunk_header + 4);\n        long chunk_data_pos = ftell(fp);\n        if (chunk_data_pos < 0) {\n            fclose(fp);\n            return 0;\n        }\n\n        if (memcmp(chunk_header, \"fmt \", 4) == 0) {\n            if (chunk_size < 16) {\n                fprintf(stderr, \"Invalid WAV: fmt chunk too short\\n\");\n                fclose(fp);\n                return 0;\n            }\n            uint8_t fmt_buf[16];\n            if (fread(fmt_buf, 1, sizeof(fmt_buf), fp) != sizeof(fmt_buf)) {\n                fprintf(stderr, \"Invalid WAV: truncated fmt chunk\\n\");\n                fclose(fp);\n                return 0;\n            }\n            fmt_audio_format = read_le16(fmt_buf + 0);\n            fmt_channels = read_le16(fmt_buf + 2);\n            fmt_sample_rate = read_le32(fmt_buf + 4);\n            fmt_bits_per_sample = read_le16(fmt_buf + 14);\n            have_fmt = 1;\n        } else if (memcmp(chunk_header, \"data\", 4) == 0) {\n            data_offset = chunk_data_pos;\n            data_size = chunk_size;\n            have_data = 1;\n        }\n\n        long next_chunk = chunk_data_pos + (long)chunk_size + (chunk_size & 1u);\n        if (fseek(fp, next_chunk, SEEK_SET) != 0) break;\n    }\n\n    if (!have_fmt || !have_data) {\n        fprintf(stderr, \"Invalid WAV: missing fmt/data chunk\\n\");\n        fclose(fp);\n        return 0;\n    }\n    if (fmt_audio_format != 1 || fmt_bits_per_sample != 16) {\n        fprintf(stderr, \"Unsupported WAV: only PCM 16-bit is supported\\n\");\n        fclose(fp);\n        return 0;\n    }\n    if (fmt_channels == 0) {\n        fprintf(stderr, \"Unsupported WAV: invalid channel count\\n\");\n        fclose(fp);\n        return 0;\n    }\n\n    if (fseek(fp, data_offset, SEEK_SET) != 0) {\n        perror(\"fseek\");\n        fclose(fp);\n        return 0;\n    }\n\n    size_t bytes_per_frame = (size_t)fmt_channels * sizeof(int16_t);\n    if (bytes_per_frame == 0 || data_size < bytes_per_frame) {\n        fprintf(stderr, \"Unsupported WAV: invalid data size\\n\");\n        fclose(fp);\n        return 0;\n    }\n\n    size_t total_frames = data_size / bytes_per_frame;\n    int16_t* raw = (int16_t*)malloc(total_frames * bytes_per_frame);\n    g_audio_data = (int16_t*)malloc(total_frames * sizeof(int16_t));\n    if (!raw || !g_audio_data) {\n        fprintf(stderr, \"malloc failed\\n\");\n        fclose(fp);\n        free(raw);\n        free(g_audio_data);\n        g_audio_data = NULL;\n        return 0;\n    }\n    if (fread(raw, bytes_per_frame, total_frames, fp) != total_frames) {\n        fprintf(stderr, \"Invalid WAV: truncated data chunk\\n\");\n        fclose(fp);\n        free(raw);\n        free(g_audio_data);\n        g_audio_data = NULL;\n        return 0;\n    }\n    fclose(fp);\n\n    // Downmix multi-channel PCM to mono.\n    for (size_t i = 0; i < total_frames; i++) {\n        int32_t sum = 0;\n        for (uint16_t ch = 0; ch < fmt_channels; ch++) {\n            sum += raw[i * fmt_channels + ch];\n        }\n        g_audio_data[i] = (int16_t)(sum / (int32_t)fmt_channels);\n    }\n    free(raw);\n    g_audio_len = total_frames;\n    g_sample_rate = (double)fmt_sample_rate;\n\n    // Normalize audio peak safely with 32-bit abs.\n    int32_t max_val = 0;\n    for (size_t i = 0; i < g_audio_len; i++) {\n        int32_t v = g_audio_data[i];\n        int32_t a = (v < 0) ? -v : v;\n        if (a > max_val) max_val = a;\n    }\n\n    if (max_val > 0 && max_val < 32000) {\n        double scale = 32000.0 / (double)max_val;\n        printf(\"Normalizing audio (Peak: %d, Scale: %.2f)...\\n\", max_val, scale);\n        for (size_t i = 0; i < g_audio_len; i++) {\n            int32_t scaled = (int32_t)lrint((double)g_audio_data[i] * scale);\n            if (scaled > 32767) scaled = 32767;\n            if (scaled < -32768) scaled = -32768;\n            g_audio_data[i] = (int16_t)scaled;\n        }\n    } else {\n        printf(\"Audio peak is %d (already loud enough or silence).\\n\", max_val);\n    }\n\n    // Speech-focused pre-EQ: DC/high-pass + low-pass + slight presence boost.\n    {\n        const double hp_fc = 180.0;\n        const double lp_fc = 3400.0;\n        const double dt = 1.0 / g_sample_rate;\n        const double rc_hp = 1.0 / (2.0 * M_PI * hp_fc);\n        const double hp_a = rc_hp / (rc_hp + dt);\n        const double lp_a = exp(-2.0 * M_PI * lp_fc / g_sample_rate);\n        const double presence = 0.35;\n\n        double hp_prev_x = 0.0;\n        double hp_prev_y = 0.0;\n        double lp_state = 0.0;\n        double prev_lp = 0.0;\n\n        for (size_t i = 0; i < g_audio_len; i++) {\n            double x = (double)g_audio_data[i] / 32768.0;\n            double hp = hp_a * (hp_prev_y + x - hp_prev_x);\n            hp_prev_x = x;\n            hp_prev_y = hp;\n\n            double lp = (1.0 - lp_a) * hp + lp_a * lp_state;\n            lp_state = lp;\n\n            double y = lp + presence * (lp - prev_lp);\n            prev_lp = lp;\n            if (y > 1.0) y = 1.0;\n            if (y < -1.0) y = -1.0;\n            g_audio_data[i] = (int16_t)lrint(y * 32767.0);\n        }\n    }\n\n    // AGC + limiter for low-RMS speech.\n    double attack = exp(-1.0 / (g_sample_rate * 0.002));   // 2ms attack\n    double release = exp(-1.0 / (g_sample_rate * 0.120));  // 120ms release\n    double env = 1e-4;\n    const double limiter_drive = 2.8;\n    const double limiter_norm = 1.0 / tanh(limiter_drive);\n\n    for (size_t i = 0; i < g_audio_len; i++) {\n        double x = (double)g_audio_data[i] / 32768.0;\n        double ax = fabs(x);\n        double coeff = (ax > env) ? attack : release;\n        env = coeff * env + (1.0 - coeff) * ax;\n\n        double gain = g_agc_target_env / (env + 1e-5);\n        if (gain < 1.0) gain = 1.0;\n        if (gain > g_agc_max_gain) gain = g_agc_max_gain;\n\n        double y = x * gain * g_agc_makeup;\n        y = tanh(y * limiter_drive) * limiter_norm;\n\n        int32_t out = (int32_t)lrint(y * 32767.0);\n        if (out > 32767) out = 32767;\n        if (out < -32768) out = -32768;\n        g_audio_data[i] = (int16_t)out;\n    }\n\n    printf(\"Loaded WAV: %u Hz, %u ch, %u bytes, %zu frames\\n\",\n           fmt_sample_rate, fmt_channels, data_size, g_audio_len);\n    return 1;\n}\n\nint main(int argc, char* argv[]) {\n    mach_timebase_info(&timebase_info);\n    if (argc != 2) {\n        fprintf(stderr, \"Usage: %s input.wav\\n\", argv[0]);\n        return 1;\n    }\n\n    g_density_exp = get_env_double(\"SBR_DENSITY_EXP\", 0.55);\n    g_density_depth = get_env_double(\"SBR_DENSITY_DEPTH\", 0.44);\n    g_agc_target_env = get_env_double(\"SBR_AGC_TARGET\", 0.36);\n    g_agc_makeup = get_env_double(\"SBR_AGC_MAKEUP\", 2.6);\n    g_agc_max_gain = get_env_double(\"SBR_AGC_MAX_GAIN\", 96.0);\n    g_packet_ms = get_env_double(\"SBR_PACKET_MS\", (double)PACKET_MS);\n    if (g_density_exp < 0.05) g_density_exp = 0.05;\n    if (g_density_exp > 1.0) g_density_exp = 1.0;\n    if (g_density_depth < 0.05) g_density_depth = 0.05;\n    if (g_density_depth > 0.495) g_density_depth = 0.495;\n    if (g_agc_target_env < 0.05) g_agc_target_env = 0.05;\n    if (g_agc_target_env > 0.8) g_agc_target_env = 0.8;\n    if (g_agc_makeup < 0.2) g_agc_makeup = 0.2;\n    if (g_agc_makeup > 8.0) g_agc_makeup = 8.0;\n    if (g_agc_max_gain < 1.0) g_agc_max_gain = 1.0;\n    if (g_agc_max_gain > 128.0) g_agc_max_gain = 128.0;\n    if (g_packet_ms < 5.0) g_packet_ms = 5.0;\n    if (g_packet_ms > 2000.0) g_packet_ms = 2000.0;\n\n    if (!load_wav(argv[1])) return 1;\n\n    g_mem = malloc(MEM_SIZE);\n    memset(g_mem, 0xFF, MEM_SIZE);\n\n    barrier_init(&g_barrier_start, NUM_THREADS + 1);\n    barrier_init(&g_barrier_end, NUM_THREADS + 1);\n\n    pthread_t threads[NUM_THREADS];\n    for (int i = 0; i < NUM_THREADS; i++) {\n        pthread_create(&threads[i], NULL, worker_thread, (void*)(size_t)i);\n    }\n\n    set_realtime(0);\n    printf(\"M1 WAV Player (Stochastic Density). Playing...\\n\");\n    printf(\"Mod params: exp=%.3f depth=%.3f agc_target=%.3f makeup=%.2f max_gain=%.1f packet_ms=%.0f\\n\",\n           g_density_exp, g_density_depth, g_agc_target_env, g_agc_makeup, g_agc_max_gain, g_packet_ms);\n\n    size_t current_idx = 0;\n    size_t samples_per_packet = (size_t)(g_sample_rate * g_packet_ms / 1000.0);\n\n    while (current_idx < g_audio_len && g_running) {\n        size_t remaining = g_audio_len - current_idx;\n        size_t chunk = (remaining > samples_per_packet) ? samples_per_packet : remaining;\n        \n        g_packet_start_sample = current_idx;\n        g_packet_num_samples = chunk;\n        \n        barrier_wait(&g_barrier_start);\n        barrier_wait(&g_barrier_end);\n        \n        current_idx += chunk;\n        printf(\"\\rProgress: %zu / %zu samples\", current_idx, g_audio_len);\n        fflush(stdout);\n    }\n    \n    printf(\"\\nDone.\\n\");\n    g_running = 0;\n    barrier_wait(&g_barrier_start); \n    \n    for (int i = 0; i < NUM_THREADS; i++) pthread_join(threads[i], NULL);\n    free(g_mem);\n    free(g_audio_data);\n    return 0;\n}\n"
  },
  {
    "path": "implementations/c-neon-threads/Makefile",
    "content": "CPPFLAGS=-Wall -O2\n\nmain: main.c\n\n.PHONY: clean\n\nclean:\n\trm -f main\n"
  },
  {
    "path": "implementations/c-neon-threads/main.c",
    "content": "// SYSTEM BUS RADIO - M1 PULSE-PACKET (CUSTOM BARRIER)\n#include <stdio.h>\n#include <stdlib.h>\n#include <stdint.h>\n#include <string.h>\n#include <arm_neon.h>\n#include <time.h>\n#include <mach/mach_time.h>\n#include <mach/thread_policy.h>\n#include <mach/mach_init.h>\n#include <mach/thread_act.h>\n#include <pthread.h>\n#include <pthread/qos.h>\n\n#define NUM_THREADS 4\n#define MEM_SIZE (64 * 1024 * 1024)\n#define PACKET_MS 20 \n\nuint8_t *g_mem;\nstatic mach_timebase_info_data_t timebase_info;\nvolatile int g_running = 1;\n\n// 共有パラメータ\nvolatile uint64_t g_current_freq = 0;\nvolatile uint64_t g_current_duration_ms = 0;\n\n// 自作バリア構造体\ntypedef struct {\n    pthread_mutex_t mutex;\n    pthread_cond_t cond;\n    int count;\n    int crossing;\n    int total;\n} my_barrier_t;\n\nmy_barrier_t g_barrier_start;\nmy_barrier_t g_barrier_end;\n\nvoid barrier_init(my_barrier_t *b, int total) {\n    pthread_mutex_init(&b->mutex, NULL);\n    pthread_cond_init(&b->cond, NULL);\n    b->count = 0;\n    b->crossing = 0;\n    b->total = total;\n}\n\nvoid barrier_wait(my_barrier_t *b) {\n    pthread_mutex_lock(&b->mutex);\n    b->count++;\n    if (b->count >= b->total) {\n        b->crossing++;\n        b->count = 0;\n        pthread_cond_broadcast(&b->cond);\n    } else {\n        int my_crossing = b->crossing;\n        while (my_crossing == b->crossing) {\n            pthread_cond_wait(&b->cond, &b->mutex);\n        }\n    }\n    pthread_mutex_unlock(&b->mutex);\n}\n\nstatic inline void bus_poke(size_t offset) {\n    uint64_t v1 = 0x5555555555555555ull;\n    uint64_t v2 = 0xAAAAAAAAAAAAAAAAull;\n    asm volatile (\n        \"stnp %0, %1, [%2]\\n\" \"stnp %1, %0, [%2, #16]\\n\"\n        \"stnp %0, %1, [%2, #32]\\n\" \"stnp %1, %0, [%2, #48]\\n\"\n        : : \"r\"(v1), \"r\"(v2), \"r\"(&g_mem[offset]) : \"memory\"\n    );\n}\n\nvoid set_realtime(int affinity_tag) {\n    pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);\n    thread_affinity_policy_data_t affinity;\n    affinity.affinity_tag = affinity_tag;\n    thread_policy_set(mach_thread_self(), THREAD_AFFINITY_POLICY, (thread_policy_t)&affinity, THREAD_AFFINITY_POLICY_COUNT);\n}\n\nvoid* worker_thread(void* arg) {\n    int id = (int)(size_t)arg;\n    set_realtime(id + 1);\n\n    size_t segment = MEM_SIZE / NUM_THREADS;\n    size_t offset = id * segment;\n    size_t end_offset = offset + segment - 64;\n    size_t current = offset;\n\n    while (g_running) {\n        // 1. 開始合図待ち\n        barrier_wait(&g_barrier_start);\n        if (!g_running) break;\n\n        uint64_t freq = g_current_freq;\n        uint64_t duration = g_current_duration_ms;\n\n        if (freq > 0) {\n            uint64_t start = mach_absolute_time();\n            uint64_t end = start + (duration * 1000000ull * timebase_info.denom / timebase_info.numer);\n            uint64_t period = (1000000000ull / freq) * timebase_info.denom / timebase_info.numer;\n            uint64_t half_period = period / 2;\n\n            uint64_t now = start;\n            while (now < end) {\n                uint64_t next_edge = now + half_period;\n                // ON\n                while (mach_absolute_time() < next_edge) {\n                    bus_poke(current);\n                    current += 64;\n                    if (current >= end_offset) current = offset;\n                }\n                next_edge += half_period;\n                // OFF\n                while (mach_absolute_time() < next_edge) {\n                    asm volatile(\"yield\");\n                }\n                now = next_edge;\n            }\n        }\n\n        // 2. 終了報告\n        barrier_wait(&g_barrier_end);\n    }\n    return NULL;\n}\n\nvoid play_tone(uint64_t freq_hz, uint64_t time_ms) {\n    uint64_t remaining = time_ms;\n    while (remaining > 0) {\n        uint64_t chunk = (remaining > PACKET_MS) ? PACKET_MS : remaining;\n        \n        g_current_freq = freq_hz;\n        g_current_duration_ms = chunk;\n        \n        // START\n        barrier_wait(&g_barrier_start);\n        \n        // END WAIT\n        barrier_wait(&g_barrier_end);\n        \n        // 短い休憩 (0.5ms)\n        struct timespec ts = {0, 500000};\n        nanosleep(&ts, NULL);\n        \n        remaining -= chunk;\n    }\n}\n\nint main(int argc, char* argv[]) {\n    mach_timebase_info(&timebase_info);\n    if (argc != 2) {\n        fprintf(stderr, \"Usage: %s file.tune\\n\", argv[0]);\n        return 1;\n    }\n    FILE* fp = fopen(argv[1], \"r\");\n    if (!fp) { perror(\"fopen\"); return 1; }\n\n    g_mem = malloc(MEM_SIZE);\n    memset(g_mem, 0, MEM_SIZE);\n\n    barrier_init(&g_barrier_start, NUM_THREADS + 1);\n    barrier_init(&g_barrier_end, NUM_THREADS + 1);\n\n    pthread_t threads[NUM_THREADS];\n    for (int i = 0; i < NUM_THREADS; i++) {\n        pthread_create(&threads[i], NULL, worker_thread, (void*)(size_t)i);\n    }\n\n    set_realtime(0);\n    printf(\"M1 Pulse-Packet Mode (Custom Barrier). Playing...\\n\");\n\n    char buffer[128];\n    int freq_hz, time_ms;\n    while (1) {\n        if (fgets(buffer, sizeof(buffer), fp) == NULL) {\n            if (feof(fp)) {\n                rewind(fp);\n                continue;\n            }\n            break;\n        }\n        if (buffer[0] == '\\n' || buffer[0] == '#' || buffer[0] == ' ') continue;\n\n        if (sscanf(buffer, \"%d %d\", &freq_hz, &time_ms) == 2) {\n            printf(\"\\r%4d Hz \", freq_hz);\n            fflush(stdout);\n            play_tone(freq_hz, time_ms);\n        }\n    }\n\n    g_running = 0;\n    barrier_wait(&g_barrier_start); // 待機中のスレッドを解放\n    \n    for (int i = 0; i < NUM_THREADS; i++) pthread_join(threads[i], NULL);\n    \n    fclose(fp);\n    return 0;\n}\n"
  },
  {
    "path": "implementations/cpp-counter-threads/Makefile",
    "content": "all : gmain cmain\n\nclean :\n\trm -f gmain\n\trm -f cmain\n\ngrun : gmain\n\t./gmain\n\ncrun : cmain\n\t./cmain\n\n\ngmain : main.cpp\n\tg++ -Wall -O2 -std=c++11 -pthread -lrt -o gmain main.cpp\n\ncmain : main.cpp\n\tclang++ -Wall -O2 -std=c++11 -stdlib=libc++ -pthread -lrt -o cmain main.cpp\n\n\n.PHONY : all clean run\n"
  },
  {
    "path": "implementations/cpp-counter-threads/main.cpp",
    "content": "// SYSTEM BUS RADIO\n// https://github.com/fulldecent/system-bus-radio\n// Copyright 2016 William Entriken\n// C++11 port by Ryou Ezoe\n\n#include <iostream>\n#include <iomanip>\n#include <chrono>\n#include <thread>\n#include <atomic>\n#include <mutex>\n#include <condition_variable>\n#include <cstdio>  // For FILE, fopen, fgets, etc.\n#include <cstdlib> // For exit()\n\nstd::mutex m;\nstd::condition_variable cv;\nstd::chrono::high_resolution_clock::time_point mid;\nstd::chrono::high_resolution_clock::time_point reset;\n\nvoid boost_song() {\n    using namespace std::chrono;\n    while (true) {\n        std::unique_lock<std::mutex> lk{m};\n        cv.wait(lk);\n\n        std::atomic<unsigned> x{0};\n        while (high_resolution_clock::now() < mid) {\n            ++x;\n        }\n        std::this_thread::sleep_until(reset);\n    }\n}\n\nvoid square_am_signal(float frequency, float time) {\n    using namespace std::chrono;\n    std::cout << \"Playing / \" << time << \" seconds / \" << frequency << \" Hz\\n\";\n\n    seconds const sec{1};\n    nanoseconds const nsec{sec};\n    using rep = nanoseconds::rep;\n    auto nsec_per_sec = nsec.count();\n\n    nanoseconds const period(static_cast<rep>(nsec_per_sec / frequency));\n\n    auto start = high_resolution_clock::now();\n    auto const end = start + nanoseconds(static_cast<rep>(time * nsec_per_sec));\n\n    while (high_resolution_clock::now() < end) {\n        mid = start + period / 2;\n        reset = start + period;\n\n        cv.notify_all();\n        std::this_thread::sleep_until(reset);\n        start = reset;\n    }\n}\n\nint main(int argc, char* argv[]) {\n    // Launch as many threads as supported by hardware\n    for (unsigned i = 0; i < std::thread::hardware_concurrency(); ++i) {\n        std::thread t(boost_song);\n        t.detach();\n    }\n\n    // File reading logic\n    FILE* fp;\n    if (argc == 2) {\n        fp = fopen(argv[1], \"r\");\n        if (!fp) {\n            perror(\"Error opening file\");\n            exit(1);\n        }\n    } else {\n        std::cerr << \"No song file given!\\nUsage: \" << argv[0] << \" file.song\\n\";\n        exit(1);\n    }\n\n    char buffer[64] = {0}; // Buffer for reading lines from file\n    int freq_hz;\n    int time_ms;\n\n    while (true) {\n        if (fgets(buffer, sizeof(buffer) - 1, fp)) {\n            if (sscanf(buffer, \"%d %d\", &freq_hz, &time_ms) == 2) {\n                square_am_signal(1.0 * time_ms / 1000, freq_hz); // Convert ms to seconds\n            }\n        }\n\n        // Handle end of file and rewind for looping the song\n        if (feof(fp)) {\n            rewind(fp);\n        }\n    }\n\n    fclose(fp); // Close the file (though unreachable in this infinite loop)\n    return 0;\n}"
  },
  {
    "path": "tests/check-test-data-tabs.sh",
    "content": "#!/bin/bash\n\n# Ensure every line has the same number of tabs\n\nHISTOGRAM=$(cat TEST-DATA.tsv | tr -cd '\\t\\n' | tr '\\t' '.' | sed '${/^$/d;}' | sort | uniq -c)\necho $HISTOGRAM\n\nif [ $(echo $HISTOGRAM | wc -w) -eq 2 ]; then\n  echo \"✅ All lines have the same number of tabs\"\nelse\n  echo \"❌ Not all lines have the same number of tabs\"\n  exit 1\nfi"
  },
  {
    "path": "tunes/README.md",
    "content": "# Tune File Format\n\nThis file defines the `.tune` music file format.\n\nFollowing is a simple example of the beginning parts of the *Super Mario Brothers* theme song:\n\n```\n660 100\n0 150\n660 100\n0 300\n660 100\n0 300\n510 100\n```\n\n## Full specification\n\n1. Each line in the file is `<frequency> <duration>`\n   1. `<frequency>` is the frequency of the beep in Hz or `0` for silence\n   2. `<duration>` is the duration of the beep in milliseconds\n2. Line ending is unix format\n3. File extension is `.tune`\n4. Although not necessarily part of the tune, consider adding a silence at the end so that looped playback sounds good :-)\n\nNote: this is compatible with the [GRUB_INIT_TUNE](https://www.gnu.org/software/grub/manual/grub/grub.html#play) when using a `TEMPO` of 60,000 (bpm).\n"
  },
  {
    "path": "tunes/mary_had_a_little_lamb.tune",
    "content": "2673 400\n2349 400\n2093 400\n2349 400\n2673 400\n2673 400\n2673 790\n2349 400\n2349 400\n2349 790\n2673 400\n3136 400\n3136 790\n2673 400\n2349 400\n2093 400\n2349 400\n2673 400\n2673 400\n2673 400\n2673 400\n2349 400\n2349 400\n2673 400\n2349 400\n2093 790\n0 400\n"
  },
  {
    "path": "tunes/morse_code_sos.tune",
    "content": "1000 200\n0 200\n1000 200\n0 200\n1000 200\n0 200\n1000 600\n0 200\n1000 600\n0 200\n1000 600\n0 200\n1000 200\n0 200\n1000 200\n0 200\n1000 200\n0 200\n0 1400\n"
  },
  {
    "path": "tunes/smb.tune",
    "content": "660 100\n0 150\n660 100\n0 300\n660 100\n0 300\n510 100\n0 100\n660 100\n0 300\n770 100\n0 550\n380 100\n0 575\n510 100\n0 450\n380 100\n0 400\n320 100\n0 500\n440 100\n0 300\n480 80\n0 330\n450 100\n0 150\n430 100\n0 300\n380 100\n0 200\n660 80\n0 200\n760 50\n0 150\n860 100\n0 300\n700 80\n0 150\n760 50\n0 350\n660 80\n0 300\n520 80\n0 150\n580 80\n0 150\n480 80\n0 500\n510 100\n0 450\n380 100\n0 400\n320 100\n0 500\n440 100\n0 300\n480 80\n0 330\n450 100\n0 150\n430 100\n0 300\n380 100\n0 200\n660 80\n0 200\n760 50\n0 150\n860 100\n0 300\n700 80\n0 150\n760 50\n0 350\n660 80\n0 300\n520 80\n0 150\n580 80\n0 150\n480 80\n0 500\n500 100\n0 300\n760 100\n0 100\n720 100\n0 150\n680 100\n0 150\n620 150\n0 300\n650 150\n0 300\n380 100\n0 150\n430 100\n0 150\n500 100\n0 300\n430 100\n0 150\n500 100\n0 100\n570 100\n0 220\n500 100\n0 300\n760 100\n0 100\n720 100\n0 150\n680 100\n0 150\n620 150\n0 300\n650 200\n0 300\n1020 80\n0 300\n1020 80\n0 150\n1020 80\n0 300\n380 100\n0 300\n500 100\n0 300\n760 100\n0 100\n720 100\n0 150\n680 100\n0 150\n620 150\n0 300\n650 150\n0 300\n380 100\n0 150\n430 100\n0 150\n500 100\n0 300\n430 100\n0 150\n500 100\n0 100\n570 100\n0 420\n585 100\n0 450\n550 100\n0 420\n500 100\n0 360\n380 100\n0 300\n500 100\n0 300\n500 100\n0 150\n500 100\n0 300\n500 100\n0 300\n760 100\n0 100\n720 100\n0 150\n680 100\n0 150\n620 150\n0 300\n650 150\n0 300\n380 100\n0 150\n430 100\n0 150\n500 100\n0 300\n430 100\n0 150\n500 100\n0 100\n570 100\n0 220\n500 100\n0 300\n760 100\n0 100\n720 100\n0 150\n680 100\n0 150\n620 150\n0 300\n650 200\n0 300\n1020 80\n0 300\n1020 80\n0 150\n1020 80\n0 300\n380 100\n0 300\n500 100\n0 300\n760 100\n0 100\n720 100\n0 150\n680 100\n0 150\n620 150\n0 300\n650 150\n0 300\n380 100\n0 150\n430 100\n0 150\n500 100\n0 300\n430 100\n0 150\n500 100\n0 100\n570 100\n0 420\n585 100\n0 450\n550 100\n0 420\n500 100\n0 360\n380 100\n0 300\n500 100\n0 300\n500 100\n0 150\n500 100\n0 300\n500 60\n0 150\n500 80\n0 300\n500 60\n0 350\n500 80\n0 150\n580 80\n0 350\n660 80\n0 150\n500 80\n0 300\n430 80\n0 150\n380 80\n0 600\n500 60\n0 150\n500 80\n0 300\n500 60\n0 350\n500 80\n0 150\n580 80\n0 150\n660 80\n0 550\n870 80\n0 325\n760 80\n0 600\n500 60\n0 150\n500 80\n0 300\n500 60\n0 350\n500 80\n0 150\n580 80\n0 350\n660 80\n0 150\n500 80\n0 300\n430 80\n0 150\n380 80\n0 600\n660 100\n0 150\n660 100\n0 300\n660 100\n0 300\n510 100\n0 100\n660 100\n0 300\n770 100\n0 550\n380 100\n0 575\n"
  }
]