Compare commits

..

14 Commits

122 changed files with 2120 additions and 28 deletions

4
.gitignore vendored
View File

@ -1,3 +1,5 @@
resources resources
public/ public/
.DS_Store .DS_Store
content/.obsidian
node_modules

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "themes/book"] [submodule "themes/book"]
path = themes/book path = themes/book
url = https://github.com/tomoyanonymous/hugo-book.git url = https://github.com/alex-shpak/hugo-book.git

15
assets/_custom.scss Normal file
View File

@ -0,0 +1,15 @@
body {
//mod by tomoya
font-family: "Helvetica Neue",
Arial,
"Hiragino Sans",
"Hiragino Kaku Gothic ProN",
Meiryo,
sans-serif;
font-feature-settings: "palt" 1;
word-break: auto-phrase;
}
code {
font-family: monospace;
}

View File

@ -0,0 +1,13 @@
---
title: Week 11
date: 2023-06-23
weight: 11
params:
pdf_path: 2023-code-and-design-11.pdf
---
# 2023 Tokyo University of the Arts Art media Center「Code and Design」 Week 11
## Slide
{{< embed_pdf >}}

View File

@ -0,0 +1,13 @@
---
title: 第11週
date: 2023-06-23
weight: 11
params:
pdf_path: 2023-code-and-design-11.pdf
---
# 2023年 東京藝術大学 芸術情報センター開設科目 「コードとデザイン」 第11回
## スライド
{{< embed_pdf >}}

View File

@ -0,0 +1,13 @@
---
title: Week 11
date: 2023-06-23
weight: 11
params:
pdf_path: 2023-code-and-design-11.pdf
---
# 2023 Tokyo University of the Arts Art media Center「Code and Design」 Week 11
## Slide
{{< embed_pdf >}}

View File

@ -0,0 +1,61 @@
---
title: 第13週
date: 2023-06-23
weight: 13
---
# 2023年 東京藝術大学 芸術情報センター開設科目 「コードとデザイン」 第13回
## スライド
(スライド行方不明・・・多分この日はスライドなかったと思います)
## 銅箔テープで作る平面コイルのSVGを出力するProcessingスケッチ
```java
import processing.svg.*;
void setup(){
noLoop();
size(800,800);
stroke(0);
background(255);
beginRecord(SVG, "spiral.svg");
}
float diameter = 800;
void draw(){
float angle = 0;
float r = 1;
float r_step =0.4;
float angle_step = 0.1;
translate(width/2,height/2);
float x_tmp = 0;
float y_tmp = 0;
while(r < width/2-10){
r += r_step;
angle += angle_step;
float x = r*sin(angle);
float y = r*cos(angle);
line(x_tmp,y_tmp,x,y);
x_tmp = x;
y_tmp = y;
}
r+=9;
while(r > 0){
r -= r_step;
angle -= angle_step;
float x = r*sin(angle);
float y = r*cos(angle);
line(x_tmp,y_tmp,x,y);
x_tmp = x;
y_tmp = y;
}
endRecord();
}
```

View File

@ -0,0 +1,43 @@
import processing.svg.*;
void setup(){
noLoop();
size(800,800);
stroke(0);
background(255);
beginRecord(SVG, "spiral.svg");
}
float diameter = 800;
void draw(){
float angle = 0;
float r = 1;
float r_step =0.4;
float angle_step = 0.1;
translate(width/2,height/2);
float x_tmp = 0;
float y_tmp = 0;
while(r < width/2-10){
r += r_step;
angle += angle_step;
float x = r*sin(angle);
float y = r*cos(angle);
line(x_tmp,y_tmp,x,y);
x_tmp = x;
y_tmp = y;
}
r+=9;
while(r > 0){
r -= r_step;
angle -= angle_step;
float x = r*sin(angle);
float y = r*cos(angle);
line(x_tmp,y_tmp,x,y);
x_tmp = x;
y_tmp = y;
}
endRecord();
}

View File

@ -2,6 +2,8 @@
title: 第9週 title: 第9週
date: 2023-06-09 date: 2023-06-09
weight: 9 weight: 9
params:
pdf_path: 2023-code-and-design-9.pdf
--- ---
# 2023年 東京藝術大学 芸術情報センター開設科目 「コードとデザイン」 第9回 # 2023年 東京藝術大学 芸術情報センター開設科目 「コードとデザイン」 第9回

View File

@ -43,10 +43,10 @@ bookCollapseSection: true
7. [(5/26) 入力を考える(不条理なマウスを作る)](./7) 7. [(5/26) 入力を考える(不条理なマウスを作る)](./7)
8. [(6/2) 入力を考える2 - ProcessingとArduinoの連携、オブジェクト指向プログラミング一人用PONG](./8) 8. [(6/2) 入力を考える2 - ProcessingとArduinoの連携、オブジェクト指向プログラミング一人用PONG](./8)
9. [(6/9) 出力を考える - Arduinoでの音声プログラミング](./9) 9. [(6/9) 出力を考える - Arduinoでの音声プログラミング](./9)
10. (6/16) Cartesian Robotとデジタルファブリケーション (3DプリンターとGコードハック) 10. (6/16) 制作①相談1
11. (6/23) 制作①相談 11. [(6/23) デジタルファブリケーション:入力機器(可変抵抗)を作る](./11)
12. (6/30) 制作②作業 12. (6/30) 制作②相談2
13. (7/7) 制作③ 内容に応じた補足授業 13. [(7/7) デジタルファブリケーション:出力機器(スピーカー)を作る](./13)
14. (7/14) 課題発表展示 14. (7/14) 課題発表展示
15. (7/21) 展示撤収・発表ふりかえりとまとめ 15. (7/21) 展示撤収・発表ふりかえりとまとめ

View File

@ -0,0 +1,15 @@
---
title: Week 1
date: 2023-10-06
weight: 1
draft: false
params:
pdf_path: 2023-media-art-programming2-1.pdf
---
# 2023 Media Art Programming #1
## Slides
{{< embed_pdf >}}

View File

@ -0,0 +1,14 @@
---
title: 第1週
date: 2023-10-06
weight: 1
draft: false
params:
pdf_path: 2023-media-art-programming2-1.pdf
---
# 2023年 メディアアート・プログラミング 第1回
## スライド
{{< embed_pdf >}}

View File

@ -0,0 +1,12 @@
const puppeteer = require('puppeteer');
const main = async () => {
let browser = await puppeteer.launch({ headless: false });
let page = await browser.newPage();
await page.goto('https://www.youtube.com/');
await page.screenshot({ path: "./ss.png", fullPage: true });
await page.close();
await browser.close();
};
main();

View File

@ -0,0 +1,11 @@
const puppeteer = require('puppeteer');
const main = async () =>
puppeteer.launch({ headless: false })
.then(browser => browser.newPage()
.then(page => page.goto('https://www.youtube.com/')
.then(_response => page.screenshot({ path: "./ss.png" })
.then(_ => page.close()
.then(_ => browser.close())))));
main();

View File

@ -0,0 +1,21 @@
const puppeteer = require('puppeteer');
const take_screen_shot = async (browser, url, index) => {
let page = await browser.newPage();
let path = `${index}.png`;
await page.goto(url);
await page.screenshot({ path: path });
await page.close();
}
const main = async () => {
let browser = await puppeteer.launch({ headless: false ,args: ["--window-size=1920,1080"],timeout:0});
const urllist = ["https://www.youtube.com",
"https://www.google.com",
"https://www.geidai.ac.jp",
"https://yahoo.co.jp"]
await Promise.all(urllist.map((url, index) => take_screen_shot(browser, url, index)));
await browser.close();
}
main();

View File

@ -0,0 +1,13 @@
const puppeteer = require('puppeteer');
const autoScroll = require("./autoscroll.js").autoScroll;
const main = async () => {
let browser = await puppeteer.launch({ headless: false });
let page = await browser.newPage();
await page.goto('https://www.youtube.com/');
await autoScroll(page,10);
await page.screenshot({ path: "ss_full.png", fullPage: true })
await page.close();
await browser.close();
}
main();

View File

@ -0,0 +1,19 @@
const puppeteer = require('puppeteer');
const autoScroll = require("./autoscroll.js").autoScroll;
const main = async () => {
let browser = await puppeteer.launch({ headless: false });
let page = await browser.newPage();
await page.goto('https://www.youtube.com/');
for (let i = 0; i < 1000; i++) {
const urllist = await page.$$eval("a#thumbnail", elems => elems.map(elem => elem.href));
const url = urllist[3];
console.log(url)
await page.goto(url);
await autoScroll(page, 10);
await page.screenshot({ path: `${i}.png`, fullPage: true });
}
await page.close();
await browser.close();
}
main();

View File

@ -0,0 +1,15 @@
---
title: Week 10
date: 2023-12-08
weight: 10
draft: false
params:
pdf_path: 2023-media-art-programming2-10.pdf
---
# 2023 Media Art Programming #10
## Slides
{{< embed_pdf >}}

View File

@ -0,0 +1,30 @@
---
title: 第10週
date: 2023-12-08
weight: 10
draft: false
params:
pdf_path: 2023-media-art-programming2-10.pdf
---
# 2023年 メディアアート・プログラミング 第10回
## スライド
{{< embed_pdf >}}
## puppeteerのコードサンプル
`autoscroll.js`は`4_auto_scroll.js`でライブラリとして使用するだけなので、内容を理解しなくても大丈夫です。
{{< preview_code href="1_launch.js" type= "js">}}
{{< preview_code href="2_async_intro.js" type= "js">}}
{{< preview_code href="3_map_async.js" type= "js">}}
{{< preview_code href="4_auto_scroll.js" type= "js">}}
{{< preview_code href="5_youtube_hopper.js" type= "js">}}
{{< preview_code href="autoscroll.js" type= "js">}}

View File

@ -0,0 +1,18 @@
async function autoScroll(page, maxScrolls) {
await page.evaluate(async maxScrolls => {
await new Promise((then) => {
let distance = 1000;
let scrolls = 0;
let timer = setInterval(() => {
window.scrollBy(0, distance);
scrolls += 1;
if (scrolls >= maxScrolls) {
clearInterval(timer);
then();
}
}, 100);
});
}, maxScrolls);
}
exports.autoScroll = autoScroll;

View File

@ -0,0 +1,15 @@
{
"name": "20231201-scraping_test",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"jsdom": "^23.0.1",
"puppeteer": "^21.6.0",
}
}

View File

@ -0,0 +1,15 @@
---
title: Week 2
date: 2023-10-13
weight: 2
draft: false
params:
pdf_path: 2023-media-art-programming2-2.pdf
---
# 2023 Media Art Programming #2
## Slides
{{< embed_pdf >}}

View File

@ -0,0 +1,14 @@
---
title: 第2週
date: 2023-10-13
weight: 2
draft: false
params:
pdf_path: 2023-media-art-programming2-2.pdf
---
# 2023年 メディアアート・プログラミング 第2回
## スライド
{{< embed_pdf >}}

View File

@ -0,0 +1,18 @@
---
title: Week 3
date: 2023-10-20
weight: 3
params:
pdf_path: 2023-media-art-programming2-3.pdf
draft: false
---
# 2023 メディアアート・プログラミング Week 3
## Slides
{{< embed_pdf >}}
{{< button href=.Page.Params.pdf_path >}}SlidesPDF{{< /button >}}
{{< button href="slides">}}SlidesHTML{{< /button >}}

View File

@ -0,0 +1,18 @@
---
title: 第3週
date: 2023-10-20
weight: 3
draft: false
params:
pdf_path: 2023-media-art-programming2-3.pdf
---
# 2023年 メディアアート・プログラミング 第3回
## スライド
{{< embed_pdf >}}
{{< button href=.Page.Params.pdf_path >}}スライドPDF{{< /button >}}
{{< button href="slides">}}スライドHTML{{< /button >}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 547 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 KiB

View File

@ -0,0 +1,10 @@
---
title: Media Art Programming 2023 Week 3 Slides
date: 2023-10-20
bookHidden: true
---
# Slides
{{< slides_jpg >}}

View File

@ -0,0 +1,10 @@
---
title: メディアアートプログラミング 2023年 第3回 スライド
date: 2023-10-20
bookHidden: true
---
# スライド
{{< slides_jpg >}}

View File

@ -0,0 +1,477 @@
---
title: Week 4
date: 2023-10-27T16:40:01+09:00
weight: 4
params:
pdf_path: "2023-media-art-programming2-4.pdf"
---
# 2023 Media Art Programming 4th
## Slides
{{< embed_pdf >}}
{{< button href=.Page.Params.pdf_path >}} slides (PDF) {{< /button >}}
{{< button href="slides">}}slides (HTML){{< /button >}}
## Shells and Pipes
Previously we have used basic commands such as `ls` and `cd`, which take terminal text input and return text output to the terminal.
On Unix-like operating systems, the input and output of commands with simple functions can be combined to perform complex operations using a feature called **pipes**.
Take, for example, the command `say`, which can be used on macOS to read text aloud.
The following command will cause macOS to say "Hello" aloud.
```sh
say "Hello".
```
Here, a program called say receives the text `"hello"` from **standard input (stdin)**.
So what happens if there is no input at all?
```sh
say
```
When executed without input, the say command enters a mode in which it waits for text input. Here, typing "hello" and then pressing the Enter key will cause the command to read out the text. When it finishes reading, it waits for text input again.
Standard input is often given in the form of a first argument or otherwise entered by the user from the terminal when the program is executed.
A pipe can be used here to pass the result of another command to say as standard input.
```sh
echo "hello" | say
```
The `echo`` command simply writes arbitrary text to standard output, while ``say`` is executed without arguments as before, but instead of going into interactive mode, it reads the "hello" given by ``echo` and exits.
Instead of `echo`, you can use a command that writes a file to standard output, such as `cat hogehoge.txt`, to read an arbitrary text file.
## Files and Devices
By the way, files on storage are not the only things that `cat` can open; Unix-like operating systems can also take hardware information on your computer (e.g., CPU temperature, hard disk RPM, etc.) just as they can handle files.
These devices reside in the `/dev` directory. This directory is not accessible from the Finder.
```sh
ls /dev
```
There are many devices out there, but it is difficult to guess their contents from their names. Let's try to use `urandom`, a hardware random number generator on your computer.
On computers, random numbers are often treated as algorithmic sequences of numbers, which means that knowing the initial value of a random number can predict subsequent random number sequences, so security-critical random number generation is often based on time or hardware random number generators.
Since opening with `cat` produces a tremendous number of random numbers and causes the terminal to freeze, let's use the head command to extract only the first few lines. Since `urandom` writes out random numbers as binary, it will include many that cannot be encoded as strings.
```sh
head /dev/urandom
```
And although not available today, in the past Linux had a virtual device called `/dev/dsp` that could write waveform data directly to the audio driver when written via pipe. Currently in Linux, the command `aplay` can do the same thing.
There is an attempt to take advantage of this mechanism, called **Bytebeat**, to generate sound in as short and simple a program as possible.
## Bytebeat
{{< youtube tCRPUv8V22o>}}
Bytebeat was first published by viznut in 2011 in a video on Youtube, which spread through commentary on his blog.
[Algorithmic symphonies from one line of code -- how and why?(2011)](http://countercomplex.blogspot.com/2011/10/algorithmic-symphonies-from- one-line-of.html)
Later, several environments were created that allow similar code to be executed on a web browser.
https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html
https://sarpnt.github.io/bytebeat-composer
Let's try Bytebeat the old-fashioned (?) way of actually creating binary data. Let's try it the old-fashioned (?) way of actually creating binary data.
Bytebeat was originally created with the following C program.
```c
main(t){for(;;t++)putchar(((t<<1)^((t<<1)+(t>>7)&t>>12))|t>>(4-(1^7&(t>>>19)))|t>>7);}
```
This C code is compressed to the extreme, so if we write it a little more carefully, it looks like this
```c
int main(int t){
for(;;t++){
putchar(((t<<1)^((t<<1)+(t>>7)&t>>12))|t>>(4-(1^7&(t>>>19)))|t>>7);
}
}
```
C program defines a function called `main`, which is the entrance to be executed by the program.
In an infinite loop with `for`, `t` starts at 0 at the beginning of the program and is increased by 1 for each loop. So this is the virtual time.
`putchar` is one of the most primitive functions in C, writing a single byte of data to the standard output.
When this t is calculated with various operations, one byte of data (0~255) is output as the value of one sample waveform (≈voltage, air pressure).
Data written to `/dev/dsp` is interpreted as 1 byte 1 sample with a sampling rate of 8000 Hz.
This time, we will use Node.js instead of C, which is difficult to build an environment for, and a program called `ffmpeg` so that it can be run outside of Linux.
## What is ffmpeg?
`ffmpeg` is a tool for converting files and data streams of various formats.
For example, you can convert a wav file to an mp3 file, receive internet radio and export it to a file, or conversely, play an audio file and host internet radio.
Because of its modularity and ability to convert so many different formats, it is no exaggeration to say that ffmpeg is behind most of the distribution services in the world.
### Installing ffmpeg
You can install `ffmpeg` with Homebrew. Please note that it takes a long time because of many dependent libraries.
```sh
brew install ffmpeg
```
### ffmpeg and ffplay commands
When you install `ffmpeg`, you can use the command `ffplay` at the same time.
ffplay` is a universal audio/video playback tool that allows you to play the second part of ffmpeg on your system instead of exporting or streaming files.
For example, you can use the following commands to play a normal audio file.
```sh
ffplay hoge.wav
```
You can also listen to Internet radio. You can receive NHK-FM (Tokyo) by opening the following URL. [^nhkurl]
[^nhkurl]: You can get a list of URLs for NHK web radio here. http://www.nhk.or.jp/radio/config/config_web.xml
```sh
ffplay https://radio-stream.nhk.jp/hls/live/2023507/nhkradiruakfm/master.m3u8
```
During playback, the standard view of the frequency distribution is the spectrogram.
![Screenshot of playback while displaying the spectrogram in ffplay...](radio1.png)
Pressing the w key with focus on this window allows you to switch between modes of waveform display. [^view]
[^view]: Optionally, `-showmode 0` will display 0: video (hidden for audio files), 1: waveform, 2: spectrogram.
![Screenshot of the audio waveform being played while it is displayed by ffplay]. (radio2.png)
### Listening to Audacity with ffmpeg
Last time, we did "Listen to Audacity with Audacity". If you do that again with ffplay, you will get the following command.
```sh
cat '/Applications/Audacity.app/Contents/MacOS/Audacity' | ffplay -f u8 -i pipe:0 -ar 44k -ac 1
```
This time, let's interpret the data as 1 byte per sample, the sample rate as 44100 (can be abbreviated as 44k), and the number of audio channels as mono.
Normally, ffplay infers the format of the data from the file extension or file header, but this time, since it reads the raw data directly, we need to specify the format as an option. This option corresponds directly to the "Import Raw Data" option we used in Audacity.
![](slides/2023-media-art-programming2-4.011.jpeg)
## Manipulate byte strings with Javascript!
We've piped the raw byte data into ffplay and listened to it. Now it's time to create the code to generate the byte data.
Javascript inherently makes no distinction between numeric byte sizes, etc. (everything is handled in real numbers, 64-bit floating point format in many environments).
The only way to handle numerical data with a defined type is by using an array with a specified type, in this case `Uint8Array`.
With this method, it is a little difficult to continuously write to standard output, so let's first write the binary data to a file once, and then read it out with cat and pipe it as before.
The minimum program to create Bytebeat this time is as follows.
```js {title = "bytebeat.js"}
const fs = require("fs");
const sample_rate = 8000;
const seconds = 5;
const byte_length = sample_rate*seconds;
const bytebeat = t =>
(t*(1+(5&t>10))*(3+(t>>17&1?(2^2&t>14)/3:3&(t>>13)+1))>>(3&t>9))&(t&4096?(t*(t^t%9)|t>3)>>1:255);
const data = Uint8Array.from({ length: byte_length },.
(v, t) => bytebeat(t)
);
fs.writeFile("jsbytebeat.hex",data, err => {} );
```
Let's look at them in turn.
```js
const fs = require("fs");
```
This line is loading the library for the final file write. Don't worry about it too much.
```js
const sample_rate = 8000;
const seconds = 5;
const byte_length = sample_rate*seconds;
```
The first two lines specify the sampling rate (how many samples per second of resolution to pack into the data) and the length of the audio waveform to be generated in seconds.
Once these two values are determined, you know how many bytes of data should be generated in the end. That is the `length`.
``js
const bytebeat = t =>
(t*(1+(5&t>10))*(3+(t>>17&1?(2^2&t>14)/3:3&(t>>13)+1))>>(3&t>>9))&(t&4096?(t*(t^t%9)|t>3)>>1:255);
```
This line is the Bytebeat program that will eventually generate the waveform. This writing style using `=>` is an abbreviation of the function definition.
```js
function bytebeat(t) {
return (t*(1+(5&t>10))*(3+(t>>17&1?(2^2&t>14)/3:3&(t>>13)+1))>>(3&t>>9))&(t&4096?(t*(t^t%9)|t>3)>>1:255);
}
```
This definition is exactly the same. The way it is written is a matter of taste, but note that return can only be omitted in the above way (even in the above way, return is still required if the `=>` is followed by curly braces `{}`).
``js
const data = Uint8Array.from({ length: byte_length },.
(v, t) => bytebeat(t)
);
```
Now we create an array of unsigned 8-bit integers. There are many ways to do this, but in this case we will use the `from`` method with the ``length` and the initialization function.
In `{length:byte_length}` we specify that we want to create an array of 8000*5=400000 samples calculated earlier.
`(v, t) => bytebeat(t)` is an initialization process that takes the index of an array called t, puts it into the bytebeat function, and stores the converted values in the array in order.
```js.
fs.writeFile("jsbytebeat.hex",data, err => {} );
```
Here we finally save the resulting byte sequence. The third argument, `err => {}`, indicates that no action is to be taken in error handling. The third argument, `err => {}`, refers to doing nothing in error handling.
Now, let's run this as `bytebeat.js` in the terminal.
At this time, remember that the `-ar` option in ffplay should match the sampling rate specified in the source code.
```sh
node bytebeat.js
cat jsbytebeat.hex | ffplay -f u8 -i pipe:0 -ar 8k -ac 1
```
If all goes well, 5 seconds of audio should play and stop.
You can also use ffmpeg instead of ffplay to output it again as a wav file.
```sh
cat jsbytebeat.hex | ffmpeg -f u8 -ar 8k -i pipe:0 -c:a pcm_u8 -ac 1 -y jsbytebeat.wav
```
### Let's take a quick look at the waveforms.
Let's try the bytebeat function as a function that just returns t as it is.
```js
const bytebeat = t =>
t
```
In this case, the value of t itself keeps rising without limit up to several hundred thousand and so on, but when it is finally written to `Uint8Array`, only the lower 8 bits of the integer portion are written. What this means is that after rising from 0 to 255, it returns to 0 again.
If you open this `jsbytebeat.hex` with 0~255 written in it with VSCode's Hex Editor, it looks like the following.
![Visual Studio Code screenshot showing binary data with continuous 0~255 in the Binary Editor]. (binary.png)
You can see that the numbers rise in sequence from 00 to FF (255) and back to 00 again.
However, it is difficult to understand what kind of waveform is being generated just by looking at the binary directly in the Hex Editor.
You can export the data to ffmpeg and view it in Audacity, or use ffplay's waveform display mode, but since we are here, let's plot the data in a simple way.
Add the following line to the second half of bytebeat.js as shown above.
```js
let file = fs.createWriteStream("graph.txt");
for (byte of data){
let txt = "";
for (i = 0; i < byte; i++) {
txt += "|"; }
}
txt += "\n"; }
file.write(txt);
}
file.end(); }
```
The above code reads one byte of the `data` array, writes a character (|) for the numeric value of the data, breaks the line, reads the next byte again ......, and repeats to create a file called graph.txt.
Now when you run `node bytebeat.js` again, it will create `graph.txt` in the directory.
If you open this in VSCode, it should look like this.
![](graph.png)
Although the display will vary depending on the size of the characters and the text wrapping settings, a sawtooth waveform is plotted by increasing the number of characters by one per line.
### Let's make it possible to run the program continuously.
Writing the contents of a `Uint8Array` to standard output is relatively easy with `process.stdout.write(data)`, but it is surprisingly troublesome to make it continue to do so permanently.
For now, let's change the code that just generated the array so that it can be written to standard output.
```js
const data = Uint8Array.from({ length: length },
(v, t) => bytebeat(t)
);
process.stdout.write(data);
```
Let's call this file `bytebeat_stream.js`.
Now we can pipe the output of node directly and play it back for 5 seconds.
```sh
node bytebeat_stream.js | ffplay -f u8 -i pipe:0 -ar 8k -ac 1
```
Eventually I want to be able to do this continuously, so I'll create an infinite loop like this.
```js
while(true){
const data = Uint8Array.from({ length: length },.
(v, t) => bytebeat(t)
);
process.stdout.write(data);
}
```
There are two problems with this code: first, t is an index created during array creation, so it resets to 0 every 5 seconds. Therefore, t should be defined as a global variable on the outside and updated.
```js
let t = 0;
while(true){
const data = Uint8Array.from({ length: length },
(v, _t) => {
const res = bytebeat(t);
t++;
return res;
}
);
process.stdout.write(data);
}
```
Declare `t` with `let` instead of `const` to declare it as a variable to be rewritten later.
Also, the argument `(v,t)` used to generate the original array is no longer used, so change it to `_t` so that the name is not covered by the variable `t`.
Another problem is that the speed at which the byte sequence is generated is by far faster than it is actually consumed by the audio driver.
(This is also the specification of the original C program, so you don't have to worry about it too much, but...) If unused data is generated endlessly and passed through a pipe, it can eat up a lot of memory.
There are several ways to achieve this, but a simple way is to use the `setInterval` function, which keeps executing a specific function at a fixed interval.
First, the part of the code that writes to standard output for 5 seconds should be a function called `mainProcess`.
```js
const mainProcess = ()=> {
const data = Uint8Array.from({ length: length },
(v, _t) => {
const res = bytebeat(t);
t += 1;
return res
}
);
process.stdout.write(data);
};
```
This is specified in `setInterval`. The second argument specifies the interval between executions, and the unit of the number at this time is milliseconds. So, divide the number of seconds specified in `seconds` by 1000.
```js
setInterval(mainProcess,seconds / 1000.0);
```
In practice, the `mainProcess` should take some time to execute, so if you are generating exactly 5 seconds and waiting for 5 seconds, there may be cases where the audio driver fails because there is not enough data to write to the audio driver. In such cases, you may want to use `1010.0` instead of `1000.0` to speed up the execution.
That's how the finished version of `bytebeat_stream.js` looks like this
```js
const sample_rate = 8000;
const seconds = 1;
const length = sample_rate * seconds;
const bytebeat = t =>
(((t >> 10 ^ t >> 11) % 5) * t >> 16) * ((t >> 14 & 3 ^ t >> 15 & 1) + 1) * t % 99 + ((3 + (t >> 14 & 3) - (t >> 16 & 1)) / 3 * t % 99 & 64);
let t = 0;
const mainProcess = () =>{
const data = Uint8Array.from({ length: length },
(v, _t) => {
const res = bytebeat(t);
t += 1;
return res
}
);
process.stdout.write(data);
};
setInterval(mainProcess,seconds / 1000.0);
```
## Supplemental (memorandum)
### Handling of integer operations in Javascript
It is actually not so obvious that the conversion of numeric types when finally packing integer values into a Uint8Array in JS is the same as in Bytebeat in C.
For example, if you open a Node.js REPL and do some calculations,
```
> 1<<30
1073741824
> 1<<31
-2147483648
> 1<<32
1
> (1<<31)+0.1
-2147483647.9
> 1.1<<31
-2147483648
> 0.9<<31
0
> 2 ** 52
4503599627370496
> 2**52+1
4503599627370497
> 2 ** 53
9007199254740992
> 2 ** 53 +1
9007199254740992
```
and so on. From here, without looking up the specification, we can roughly do the following
- The internal representation of numbers in JS is 64-bit floating point (`double` in C).
- The mantissa part (integer value when the exponent part is 0) is signed 53bit (if you take out the upper 32bit, it becomes C's `int`/`int32_t`).
- When performing bitwise operations, once truncate to an integer, and then calculate as an operation between signed 32 bits.
- When putting into `Uint8Array` and so on, the lower bits are cut out and casted, and the lower 32 bits are also used when casted from a 52-bit integer to a 32-bit integer.
I checked later and it seems that this specification is generally correct.
This behavior seems to eventually lead to the same result as the case where `t` is cast to type `char` when doing `putchar` using the first argument of the `main` function, that is, `int`, when doing Bytebeat in C language.
It seems to be established on the balance of quite exquisite specification that the same sound can be easily made in C and Javascript. I'm not sure if they were anticipating that much or not...
However, if you think about it, when you update the time in t++, it seems that in JavaScript, after being added up to 2^52, the increment will continue with a loss of precision instead of returning to 0. For example, how much does the accuracy start to drop for mono at a sample rate of 44.1 kHz?
```sh
> (2**52 /44100)/(60*60*24*365)
3238.2813460788693
```
It looks like we have nothing to worry about for 3238 years.

View File

@ -0,0 +1,476 @@
---
title: 第4週
date: 2023-10-27T16:40:01+09:00
weight: 4
params:
pdf_path: "2023-media-art-programming2-4.pdf"
---
# 2023年 メディアアート・プログラミング 第4回
## スライド
{{< embed_pdf >}}
{{< button href=.Page.Params.pdf_path >}}スライドPDF{{< /button >}}
{{< button href="slides">}}スライドHTML{{< /button >}}
## シェルとパイプ
前回まで`ls`や`cd`のような基礎コマンドを使ってきましたが、これらはターミナルのテキスト入力を受け取るとターミナルにテキスト出力を返すものでした。
Unix系のOSでは、単純な機能を持つコマンドの入出力を**パイプ**と呼ばれる機能を使い、組み合わせて複雑な処理を実行できます。
例えばmacOSで使えるテキストを音声で読み上げるコマンド`say`を例にしましょう。
次のコマンドを実行すると、macOSが音声で”こんにちは”と喋ってくれます。
```sh
say "こんにちは"
```
ここでは、sayというプログラムが**標準入力stdin**から`"こんにちは"`というテキストを受け取っています。
では、入力が全くないとどうなるでしょうか?
```sh
say
```
このように入力なしで実行すると、sayコマンドはテキスト入力を待機するモードになります。ここで、"こんにちは"と入力してからEnterキーを押すと読み上げが発生します。読み上げ終わると再度テキスト入力を待機します。
標準入力は多くの場合、第1引数の形で与えられるか、そうでなければプログラム実行時にユーザーがターミナルから入力するような形をとります。
ここでパイプを使うと、別のコマンドの結果を標準入力としてsayに渡すことができます。
```sh
echo "こんにちは" | say
```
`echo`は単に任意のテキストを標準出力に書き込むコマンドです。sayは先ほど同様に引数なしで実行しましたが、対話モードにはならず`echo`から与えられた"こんにちは"を読んで終了します。
`echo`の代わりに`cat hogehoge.txt`のようなファイルを標準出力に書き出すコマンドを使えば、任意のテキストファイルを読み上げさせることもできます。
## ファイルとデバイス
ところで、`cat`が開けるものはストレージ上のファイルだけではありません。Unix系のOSはファイルを扱うのと同じようにコンピューター上のハードウェア情報例えばCPUの温度や、ハードディスクの回転数などを取ることもできます。
こうしたデバイスは`/dev`ディレクトリに存在します。このディレクトリはFinderなどから覗くことはできません。
```sh
ls /dev
```
たくさんのデバイスがありますが、名前から内容を推測することは難しいです。Bluetoothで繋がってるデバイスなどはその片鱗が伺えます。ここでは試しに、コンピューター上に搭載されているハードウェア乱数生成機`urandom`を使ってみましょう。
コンピューター上では乱数をアルゴリズミックな数列として扱うことが多いですが、これは乱数の初期値を知っていると続く乱数列を予測できてしまうことでもあるため、セキュリティ的に重要な乱数の生成は時刻やハードウェア乱数生成機を元に使うことが多いのです。
`cat`で開くとものすごい数の乱数が出てターミナルが固まってしまうので、最初の数行だけを取り出すheadコマンドを使ってみましょう。`urandom`はバイナリとしての乱数を書き出すため、文字列としてエンコードできないものもたくさん含まれてきます。
```sh
head /dev/urandom
```
そして、現在は使えないものの、昔のLinuxには`/dev/dsp`という、パイプで書き込むと直接オーディオドライバに波形データを書き込める仮想デバイスが存在しました。現在Linuxでは`aplay`というコマンドで同様のことができます。
この仕組みを活用して、できるだけ短く単純なプログラムで音を生成する**Bytebeat**という試みがあります。
## Bytebeat
{{< youtube tCRPUv8V22o>}}
Bytebeatは2011年にviznutがYoutube上の動画で公開し、自身のブログの解説などで広がっていったものです。
[Algorithmic symphonies from one line of code -- how and why?(2011)](http://countercomplex.blogspot.com/2011/10/algorithmic-symphonies-from-one-line-of.html)
その後、Webブラウザ上でも同様のコードを実行できる環境がいくつか誕生しました。
https://greggman.com/downloads/examples/html5bytebeat/html5bytebeat.html
https://sarpnt.github.io/bytebeat-composer
今回はBytebeatを、実際にバイナリデータを作る昔ながらのやり方でやってみましょう。
Bytebeatは元々次のようなC言語のプログラムで作られていました。
```c
main(t){for(;;t++)putchar(((t<<1)^((t<<1)+(t>>7)&t>>12))|t>>(4-(1^7&(t>>19)))|t>>7);}
```
このC言語のコードは極限まで圧縮されているのでもうちょっと丁寧に書くとこうなります。
```c
int main(int t){
for(;;t++){
putchar(((t<<1)^((t<<1)+(t>>7)&t>>12))|t>>(4-(1^7&(t>>19)))|t>>7);
}
}
```
C言語のプログラムは`main`という関数を定義するとそれがプログラムで実行される入り口になります。
`for`による無限ループの中で、`t`がプログラム開始時には0でスタートし、ループごとに1増えています。これが仮想的な時間になるわけですね。
`putchar`は標準出力に1バイトのデータを書き込む、C言語の中でも最も原始的な関数の1つです。
このtを様々な演算で計算すると、1バイト分のデータ0~255がある1サンプルの波形の値≒電圧、空気圧になって出力されます。
`/dev/dsp`に書き込んだデータは1バイト1サンプル、サンプリングレート8000Hzとして解釈されます。
今回は、環境構築が大変なC言語の代わりにNode.jsを使い、Linux以外でも実行できるように`ffmpeg`というプログラムを使用します。
## ffmpegとは
`ffmpeg`は様々なフォーマットのファイルやデータストリームを変換するためのツールです。
例えばwavファイルをmp3ファイルに変換したり、インターネットラジオを受信してファイルに書き出したり、逆に音声ファイルを再生してインターネットラジオをホストするようなこともできます。
非常に多種多様なフォーマットの変換が可能でモジュラーな作りになっているため、世の配信サービスの裏側では大抵ffmpegが動いていると思っても過言ではありません。
### ffmpegのインストール
`ffmpeg`はHomebrewでインストールできます。依存ライブラリが多いため時間がかかるので注意してください。
```sh
brew install ffmpeg
```
### ffmpegとffplayコマンド
`ffmpeg`をインストールすると、`ffplay`というコマンドも同時に使えるようになります。
`ffplay`はffmpegの後段をファイル書き出しやストリーミングではなくシステム上で再生するようにした、いわば万能オーディオ/ビデオ再生ツールです。
例えば普通のオーディオファイルの再生は次のようなコマンドで可能です。
```sh
ffplay hoge.wav
```
また、インターネットラジオも聞けます。以下のURLを開くとNHK-FM東京を受信できます。[^nhkurl]
[^nhkurl]: NHKのWebラジオのURL一覧はここから取得できます。 http://www.nhk.or.jp/radio/config/config_web.xml
```sh
ffplay https://radio-stream.nhk.jp/hls/live/2023507/nhkradiruakfm/master.m3u8
```
再生中は標準ではスペクトログラムという周波数分布のビューが表示されます。
![ffplayでスペクトログラムを表示しながら再生している様子のスクリーンショット。](radio1.png)
このウィンドウにフォーカスをした状態でwキーを押すと、波形表示のモードと切り替えができます。[^view]
[^view]: オプションで`-showmode 0`のようにすると0:ビデオ音声ファイルの場合非表示、1:波形、2:スペクトログラム が表示されます。
![ffplayで音声波形を表示しながら再生している様子のスクリーンショット。](radio2.png)
### Audacityをffmpegで聴く
前回、「AudacityでAudacityを聴く」というのをやりました。あれをもう一度ffplayでもやると次のようなコマンドになります。
```sh
cat '/Applications/Audacity.app/Contents/MacOS/Audacity' | ffplay -f u8 -i pipe:0 -ar 44k -ac 1
```
今回は、データを1バイト1サンプル、サンプルレートは4410044kと省略できます、オーディオチャンネル数はモラルとして解釈しましょう。
通常、ffplayは拡張子やファイルのヘッダーからデータのフォーマットを推定しますが、今回は生のデータを直接読むので、オプションとしてフォーマットを指定してあげる必要があります。このオプションは、前回Audacityでやった時の"Rawデータをインポート"のオプションと直接的に対応しています。
![](slides/2023-media-art-programming2-4.011.jpeg)
## Javascriptでバイト列を操作しよう
生のバイトデータをffplayにパイプして聴くことはできました。それではいよいよバイトデータを生成するコードを作っていきましょう。
Javascriptは本来、数値のバイトサイズなどの区別がありません全て実数、多くの環境では64bit浮動小数点のフォーマットで扱われます
唯一、数値データの型を決めて扱う方法として、型を指定した配列、今回の場合は`Uint8Array`を使うことで実現できます。
この方法だと連続して標準出力に書き込み続けるのが少し難しいため、まず一度ファイルにバイナリデータを書き出して、それを先ほどと同じくcatで読み出してパイプしてみましょう。
今回Bytebeatを作る最小のプログラムは次のようなものになります。
```js {title = "bytebeat.js"}
const fs = require("fs");
const sample_rate = 8000;
const seconds = 5;
const byte_length = sample_rate*seconds;
const bytebeat = t =>
(t*(1+(5&t>>10))*(3+(t>>17&1?(2^2&t>>14)/3:3&(t>>13)+1))>>(3&t>>9))&(t&4096?(t*(t^t%9)|t>>3)>>1:255);
const data = Uint8Array.from({ length: byte_length },
(v, t) => bytebeat(t)
);
fs.writeFile("jsbytebeat.hex",data, err => {} );
```
順番にみていきましょう。
```js
const fs = require("fs");
```
この行は、最終的にファイル書き込みをするためのライブラリの読み込みです。あまり気にしなくても大丈夫です。
```js
const sample_rate = 8000;
const seconds = 5;
const byte_length = sample_rate*seconds;
```
はじめ2行は、サンプリングレート1秒間あたり何サンプルの解像度でデータを詰め込むかの指定、生成する音声波形の長さを何秒にするかを決めています。
この2つの値が決まれば、データを最終的に何バイト生成すればいいかがわかります。それが`length`です。
```js
const bytebeat = t =>
(t*(1+(5&t>>10))*(3+(t>>17&1?(2^2&t>>14)/3:3&(t>>13)+1))>>(3&t>>9))&(t&4096?(t*(t^t%9)|t>>3)>>1:255);
```
この行が最終的に波形を生成するBytebeatのプログラムです。この`=>`を使う書き方は関数定義の省略形です。
```js
function bytebeat(t) {
return (t*(1+(5&t>>10))*(3+(t>>17&1?(2^2&t>>14)/3:3&(t>>13)+1))>>(3&t>>9))&(t&4096?(t*(t^t%9)|t>>3)>>1:255);
}
```
この定義でも全く同じです。書き方は好みですが、returnを省略できるのは上の書き方の方だけなので注意してください上の書き方でも、`=>`の後を中括弧`{}`で囲む場合は、やはりreturnが必須です
```js
const data = Uint8Array.from({ length: byte_length },
(v, t) => bytebeat(t)
);
```
ここでunsigned 8bit 整数の配列を作成します。やり方にはいろいろありますが、今回は`from`メソッドで`length`と初期化関数を指定する方法を使いましょう。
`{length:byte_length}`では先ほど計算した8000*5=40000サンプル分の配列を生成することを指定しています。
`(v, t) => bytebeat(t)`は、tという配列のインデックスを取得してbytebeat関数に入れて変換したものを配列に順番に収めていく、という初期化の処理です。
```js
fs.writeFile("jsbytebeat.hex",data, err => {} );
```
ここでようやく、出来上がったバイト列を保存します。`"jsbytebeat.hex"`は好きなファイル名で問題ありませんが、特にフォーマットの決まっていないバイナリファイルなら拡張子は`.bin`や`.hex`などを使うことが多いです。3つ目の引数である`err => {}`はエラー処理で何もしないことを指しています。
では、これを`bytebeat.js`として、ターミナルで実行しましょう。
この時、ffplayでの`-ar`オプションはソースコード内で指定したサンプリングレートと一致させることを忘れないようにしましょう。
```sh
node bytebeat.js
cat jsbytebeat.hex | ffplay -f u8 -i pipe:0 -ar 8k -ac 1
```
うまくいけば、5秒分の音声が再生されて停止するはずです。
ffplayの代わりにffmpegでwavファイルとして改めて出力することもできます。
```sh
cat jsbytebeat.hex | ffmpeg -f u8 -ar 8k -i pipe:0 -c:a pcm_u8 -ac 1 -y jsbytebeat.wav
```
### 波形を簡易的に観察してみよう
試しにbytebeat関数をtをそのまま返すだけの関数としてみます。
```js
const bytebeat = t =>
t
```
この時、tの数値自体は数十万などまで際限なく上昇し続けますが、最終的に`Uint8Array`に書き込まれるときには整数部分下位8bitのみが書き込まれます。どういうことかというと、0~255まで上昇するとまた0に戻るのです。
この、0~255を書き込んだ`jsbytebeat.hex`をVSCodeのHex Editorで開くと次のような見た目をしています。
![バイナリエディタで0~255までが連続するバイナリデータを表示したVisual Studio Codeのスクリーンショット。](binary.png)
00からFF(255)まで順番に数値が上昇して、また00に戻っているのがわかります。
しかし、バイナリを直接Hex Editorで見るだけではあまりにどんな波形が生成されてるのかわかりにくいです。
ffmpegで書き出してAudacityで見たり、ffplayの波形表示モードを使うこともできるのですが、せっかくなので、簡単な方法でデータをプロットしてみることにしましょう。
先ほどのbytebeat.jsの後半に以下の行を追加します。
```js
let file = fs.createWriteStream("graph.txt");
for (byte of data){
let txt = "";
for (i = 0; i < byte; i++) {
txt += "|";
}
txt += "\n";
file.write(txt);
}
file.end();
```
上のコードは、`data`の配列を1バイト分読み取り、データの数値の分だけ文字|を書いて、改行し、また次の1バイトを読む……というのを繰り返し、graph.txtというファイルを作っています。
これで`node bytebeat.js`を改めて実行すると、ディレクトリに`graph.txt`が作られます。
これをVSCodeで開くとこんな感じになるはず。
![](graph.png)
文字数の大きさや、テキストの折り返し設定によってい表示は異なりますが、1行ごとに1文字ずつ増えることでコギリ状の波形がプロットできています。
### 連続して実行できるようにしよう
`Uint8Array`の中身を標準出力に書き込むの自体は、`process.stdout.write(data)`と割と簡単にできますが、それを永続的に続けられるようにするのは意外と面倒です。
とりあえず、先ほど配列生成をしたコードを標準出力に書き出せるように変えましょう。
```js
const data = Uint8Array.from({ length: length },
(v, t) => bytebeat(t)
);
process.stdout.write(data);
```
このファイルを`bytebeat_stream.js`としましょう。
これでnodeの出力を直接パイプして5秒間は再生できるようになりました。
```sh
node bytebeat_stream.js | ffplay -f u8 -i pipe:0 -ar 8k -ac 1
```
最終的にはこれを連続して行えるようにしたいので、こんな感じに無限ループを作ってみます。
```js
while(true){
const data = Uint8Array.from({ length: length },
(v, t) => bytebeat(t)
);
process.stdout.write(data);
}
```
このコードには2つの問題があります。1つ目に、tは配列生成時に作られるインデックスなので、5秒ごとに0にリセットされてしまいます。そのため、tは外側にグローバルな変数として定義して、更新するようにしましょう。
```js
let t = 0;
while(true){
const data = Uint8Array.from({ length: length },
(v, _t) => {
const res = bytebeat(t);
t++;
return res;
}
);
process.stdout.write(data);
}
```
`t`は後から書き換える変数として宣言するために`const`ではなく`let`で宣言してください。
また元々の配列生成に使っていた引数`(v,t)`はもう使わないので、変数`t`と名前が被らないように`_t`と変えておきましょう。
もう1つの問題としては、バイト列を生成する速度が実際にオーディオドライバーで消費されるよりも圧倒的に速いことです。
これは元のC言語のプログラムでもそういう仕様なのであまり気にしなくても良いのですが、、、使われることのないデータが無限に生成されてパイプに流されるとメモリを異常に食ったりするので、5秒分の配列を生成したら5秒分休むようなコードに変えましょう。
こうしたコードの実現方法はいくつかあるのですが、簡単な方法は特定の関数を一定周期で実行し続ける、`setInterval`関数を使うことです。
まず、5秒分標準出力に書き込む部分を`mainProcess`として関数にしましょう。
```js
const mainProcess = ()=> {
const data = Uint8Array.from({ length: length },
(v, _t) => {
const res = bytebeat(t);
t += 1;
return res
}
);
process.stdout.write(data);
};
```
これを、`setInterval`に指定します。第2引数に実行間隔を指定しますが、この時の数値の単位はミリ秒です。なので、`seconds`で指定した秒数を1000で割ってあげましょう。
```js
setInterval(mainProcess,seconds / 1000.0);
```
実際には、`mainProcess`の実行にも多少なり時間がかかるはずなので、ピッタリ5秒分を生成して5秒待つ、をやっているとオーディオドライバに書き込むデータが不足して落ちるケースがたまにあるようです。そうした場合`1000.0`の代わりに`1010.0`とかにして実行感覚を多少速くしてあげるのもいいでしょう。
そういうわけで、完成版`bytebeat_stream.js`は次のような感じで完成です。
```js
const sample_rate = 8000;
const seconds = 1;
const length = sample_rate * seconds;
const bytebeat = t =>
(((t >> 10 ^ t >> 11) % 5) * t >> 16) * ((t >> 14 & 3 ^ t >> 15 & 1) + 1) * t % 99 + ((3 + (t >> 14 & 3) - (t >> 16 & 1)) / 3 * t % 99 & 64);
let t = 0;
const mainProcess = () =>{
const data = Uint8Array.from({ length: length },
(v, _t) => {
const res = bytebeat(t);
t += 1;
return res
}
);
process.stdout.write(data);
};
setInterval(mainProcess,seconds / 1000.0);
```
## 補足(備忘録)
### Javascriptでの整数演算の処理について
JSで最終的に整数の値をUint8Arrayに詰め込むときの数値型の変換がC言語でのBytebeatと同じになるかどうかは実はそんなに自明ではありません。
例えばNode.jsのREPLを開いていくつか計算してみると、
```
> 1<<30
1073741824
> 1<<31
-2147483648
> 1<<32
1
> (1<<31)+0.1
-2147483647.9
> 1.1<<31
-2147483648
> 0.9<<31
0
> 2 ** 52
4503599627370496
> 2**52+1
4503599627370497
> 2 ** 53
9007199254740992
> 2 ** 53 +1
9007199254740992
```
といった感じになっています。ここから、仕様を調べずともおおよそ次のことができます。
- JSの数値の内部表現は64bit浮動小数点C言語における`double`)である。
- うち、仮数部分指数部が0の場合の整数値signed 53bitである上位32bitを抜き出すとCの`int`/`int32_t`になる)。
- &、ビット演算を行う際は、一旦整数に切り捨てをしてから、signed 32bit同士の演算として計算する。
- `Uint8Array`などに入れるときは下位ビットを切り出してキャストしている。52bitの整数から32bitにキャストされるときにも下位32ビットが使われている。
後々調べたら大体この仕様であっているようです。
この挙動が結局、C言語でBytebeatをやる際には`t`は`main`関数の第1引数、つまり`int`を使い、`putchar`をするときに`char`型にキャストされるケースと同じ結果を導くようです。
C言語とJavascriptで簡単に同じ音が鳴るのは結構絶妙な仕様のバランスの上に成立しているように見えます。そこまで見越して作っていたのかどうかはよくわかりませんが・・・。
ただ、よく考えるとt++で時刻を更新して行ったときに、JavaScriptでは2^52まで足されたあと、0に戻るのではなく精度が落ちたままインクリメントが続くことになりそうです。例えばサンプルレート44.1kHzのモノラルの場合どのくらいから精度が落ちるのでしょうか。
```sh
> (2**52 /44100)/(60*60*24*365)
3238.2813460788693
```
 
3238年間は心配なさそうです。

Binary file not shown.

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 872 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 411 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 888 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 281 KiB

View File

@ -0,0 +1,10 @@
---
title: "Media Art Programming 2 2023 Week 4 Slides"
date: 2023-10-27T16:40:01+09:00
bookHidden: true
---
# Slides
{{< slides_jpg >}}

View File

@ -0,0 +1,10 @@
---
title: "メディアアート・プログラミング2 2023年 第4回 スライド"
date: 2023-10-27T16:40:01+09:00
bookHidden: true
---
# スライド
{{< slides_jpg >}}

View File

@ -0,0 +1,15 @@
---
title: Week 5
date: 2023-10-31
weight: 5
draft: false
params:
pdf_path: 2023-media-art-programming2-5.pdf
---
# 2023 Media Art Programming #5
## Slides
{{< embed_pdf >}}

View File

@ -0,0 +1,139 @@
---
title: 第5週
date: 2023-10-31
weight: 5
draft: false
params:
pdf_path: 2023-media-art-programming2-5.pdf
---
# 2023年 メディアアート・プログラミング 第5回
## スライド
{{< embed_pdf >}}
## テキスト操作のサンプルコード
### load_text.js(ファイルの読み込み)
```js
//ファイル読み込みライブラリを使用
const fs = require("fs");
//ファイル名の指定(./はこのjsファイルと同階層にあることを明示
const src = "./henshin.txt";
//ファイルを読み込み
const bytes = fs.readFileSync(src);
//バイト列から文字列に
const txt = bytes.toString();
console.log(txt);
```
{{< button href="load_text.js">}}load_text.js{{< /button >}}
### text_as_array.js (文字列から文字をインデックスで取り出す)
文字列オブジェクトはJavascriptの中では配列のように`str[idx]`として先頭から`idx`番目の文字を取り出せます。
```js
///...以上load_text.jsと同じ
//配列として先頭から100文字コンソールに表示
for (let i =0;i<100;i++) {
console.log(txt[i])
}
```
{{< button href="text_as_array.js">}}text_as_array.js{{< /button >}}
### text_as_array2.js (文字列を文字の配列オブジェクトにする)
より明確に1文字ずつを要素とした配列オブジェクトにすることもできます。
こちらのやり方だと、配列を変換する`map`や`filter`、`reduce`などの便利な関数が使えます。
```js
///...以上load_text.jsと同じ
//文字の配列にする
const arr = Array.from(txt);
console.log(arr);
```
{{< button href="text_as_array2.js">}}text_as_array2.js{{< /button >}}
### text_sort.js (文字列を文字コード順でソート)
```js
///...以上load_text.jsと同じ
//文字の配列にする
const arr = Array.from(txt);
//配列を文字コード順でソート
const sorted = arr.sort();
//ソートした配列を(区切り文字なしで)結合
const sorted_str = sorted.join("");
//ファイルに保存
fs.writeFileSync("henshin_sorted.txt",sorted_str);
```
{{< button href="text_sort.js">}}text_sort.js{{< /button >}}
### text_reversed.js (文字列を前後逆転)
```js
///...以上load_text.jsと同じ
//文字の配列にする
const arr = Array.from(txt);
//配列を反転
const reversed = arr.reverse();
//反転した配列を(区切り文字なしで)結合
const reversed_str = reversed.join("");
//ファイルに保存
fs.writeFileSync("henshin_reversed.txt",reversed_str);
```
{{< button href="text_reversed.js">}}text_reversed.js{{< /button >}}
### text_unicode_shift.js 文字列のUnicodeコードポイントを一つずつずらす
```js
///...以上load_text.jsと同じ
//文字の配列にする
const arr = Array.from(txt);
//配列の文字コードを1つ隣へずらす
const shifted = arr.map( c => String.fromCodePoint(c.codePointAt(0)+1));
//ソートした配列を(区切り文字なしで)結合
const shifted_str = shifted.join("");
//ファイルに保存
fs.writeFileSync("henshin_shifted.txt",shifted_str);
```
{{< button href="text_unicode_shift.js">}}text_unicode_shift.js{{< /button >}}
### text_unicode_shift_grad.js 文字列のUnicodeコードポイントをずらす確率を段々上げていく
```js
///...以上load_text.jsと同じ
//文字の配列にする
const arr = Array.from(txt);
//文字コードをずらす確率
let probability = 0.0;
const shifted = arr.map(c => {
//配列の最初0%-最後:100%になるように確率を変更していく
probability += 1 / arr.length;
//randomは0~1の乱数
if (Math.random() < probability) {
//配列の文字コードを1つ隣へずらす
return String.fromCodePoint(c.codePointAt(0) + 1)
} else {
return c
}
});
//ソートした配列を(区切り文字なしで)結合
const shifted_str = shifted.join("");
//ファイルに保存
fs.writeFileSync("henshin_shifted_grad.txt", shifted_str);
```
{{< button href="text_unicode_shift_grad.js">}}text_unicode_shift_grad.js{{< /button >}}

View File

@ -0,0 +1,9 @@
//ファイル読み込みライブラリを使用
const fs = require("fs");
//ファイル名の指定(./はこのjsファイルと同階層にあることを明示
const src = "./henshin.txt";
//ファイルを読み込み
const bytes = fs.readFileSync(src);
//バイト列から文字列に
const txt = bytes.toString();
console.log(txt);

View File

@ -0,0 +1,69 @@
const fs = require("fs");
//青空文庫から落としたファイルの場合、一度shift-jisからutf-8に変換すること。
//例で使用したのはフランツ・カフカ「変身」
//https://www.aozora.gr.jp/cards/001235/card49866.html
const src = "henshin.txt";
//ファイルをテキストデータとして読み込み
const txt = fs.readFileSync(src).toString();
//長いデータの中身を見てみたい場合は、頭の1000文字だけ取り出してコンソールに流すなどする
// const summary = txt.slice(0,1000);
// console.log(summary)
// 主人公の名前だけを抜き出して空白に置き換える
const no_gregor = txt.replace(/グレゴール/gm, "《 》");
fs.writeFileSync("henshin_nogregor.txt", no_gregor);
let only_gregor = "";
let lines = txt.split(/\n/);
//逆に、主人公の名前以外は全て◻︎で置き換えてみよう
for (l of lines) {
const array = l.split("グレゴール");
if (array.length > 1) {
only_gregor += array.map((s) => "□".repeat(s.length)).join('グレゴール');
} else {
only_gregor += "□".repeat(array[0].length)
}
only_gregor += "\n"
}
fs.writeFileSync("henshin_onlygregor.txt", only_gregor);
//会話だけを抜き出してみる
let only_spoken_words = "";
for (l of lines) {
// let matches= l.match(/「(.*)」/g);
let matches = l.match(/「([^」]+)」/g);
if (matches != null) {
only_spoken_words += matches.join("\n") + "\n"
}
}
fs.writeFileSync("henshin_onlyspokenwords.txt", only_spoken_words);
//会話の中で!で終わるものだけを抜き出してみる
let excramation = "";
for (l of lines) {
let matches = l.match(/(?!。| |「)([^。 「]*?)/g);
if (matches != null) {
excramation += matches.join("\n") + "\n"
}
}
fs.writeFileSync("henshin_excramation.txt", excramation);
//テキストを文字の配列として扱い、後半に行くにつれて文字コードを確率的にずらしていく
let nextunicode = "";
let probability = 0.0;
const tarray = Array.from(txt);
for (const c of tarray) {
if (c.at(0) == "\n") {
nextunicode += "\n";
} else {
nextunicode += String.fromCodePoint(c.codePointAt(0) + Math.floor(Math.random() * probability * 6));
}
probability += 1 / tarray.length;
}
fs.writeFileSync("henshin_nextunicode.txt", nextunicode);

View File

@ -0,0 +1,12 @@
//ファイル読み込みライブラリを使用
const fs = require("fs");
//ファイル名の指定(./はこのjsファイルと同階層にあることを明示
const src = "./henshin.txt";
//ファイルを読み込み
const bytes = fs.readFileSync(src);
//バイト列から文字列に
const txt = bytes.toString();
//配列として先頭から100文字コンソールに表示
for (let i =0;i<100;i++) {
console.log(txt[i])
}

View File

@ -0,0 +1,11 @@
//ファイル読み込みライブラリを使用
const fs = require("fs");
//ファイル名の指定(./はこのjsファイルと同階層にあることを明示
const src = "./henshin.txt";
//ファイルを読み込み
const bytes = fs.readFileSync(src);
//バイト列から文字列に
const txt = bytes.toString();
//文字の配列にする
const arr = Array.from(txt);
console.log(arr);

Some files were not shown because too many files have changed in this diff Show More