Thursday, February 2, 2023

How I study Chinese

 Just a few notes on how I study Chinese.


I study daily using Spaced Repetition Flashcards (a.k.a. srf). About an hour per day.

I started using Anki, but I found the algorithm didn't work for me, causing me to become overwhelmed with big backlogs that I couldn't get through and I had to manually adjust new cards. The algorithm of srf works better for me and it regulates new cards automatically.

But there are many programs for presenting flashcards and any of them can work.

Because I started with Anki, I started with a few Anki decks. One of characters, one of Chinese provinces and cities, and one of words and sentences. I have added many of my own cards, mostly from videos I have watched.

I find the sentences harder to learn initially but easier in the long term. Individual characters are too abstract, with too many unrelated meanings. As I progress, it seems more important and more effective to be able to recognize them in the context of words (combinations of two or more characters) and sentences. Even if I knew all the meanings related to each individual character, it often leaves me unsure about the meaning of a word or sentence or, worse, with the wrong interpretation.

I watch a lot of Chinese videos with English and Chinese subtitles. Mostly modern drama with, I hope, modern vocabulary and accent.

A few years ago, when I started, I had a lot of difficulty finding good videos with clear audio and good subtitles. More recently there is a lot of good content. YouTube has many channels of good content. More than I can watch. I sometimes watch videos from Chinese sites, but many of them aren't available to me or require setting up accounts, so I don't bother. There's enough content on the free sites.

I have begun reading. I use Calibre to maintain a library of epub books but I view them in my Firefox browser using calibre-web to serve them and the @ig3/zhongwen add-on for lookup of Chinese characters.

The @ig3/zhongwen add-on is available from the Firefox add-ons website.

I use @ig3/zhongwen rather than the Zhongwen add-on because @ig3/zhongwen supports lookup of content in iframes and calibre-web presents the epub content in iframes, so the Zhongwen add-on doesn't work. I have also improved the positioning of the pop-up in @ig3/zhongwen so it doesn't cover the selected characters.

With a local Calibre library and calibre-web running on my laptop, I can read Chinese with character lookup even when I'm offline.

I'm old. My memory isn't as good as it used to be. But I am making progress learning Chinese. The stats in srf make this clear and also, when I watch videos or listen to conversations of native Chinese speakers, I can't yet follow but I begin to catch words and phrases and understand them without consciously translating them. It's always a thrill when I clearly get the meaning without having to think about it.

I only use Firefox, not Chrome, Safari, Opera or any other browser. While calibre-web might work from other browsers, I haven't tried and don't intend to support using @ig3/zhongwen from other browsers.  In particular, I have not published it as a Chrome add-on, as the original Zhongwen add-on is. I have submitted a patch to the Zhongwen add-on maintainer, for support of content in iframes, but have had no response to it and it hasn't been merged. Since then, I have made several more changes to @ig3/zhongwen, including a revision of how iframes are supported, making support more reliable.

I also produce my own epub documents for study. I first put the text into Markdown files then I use a simple script to convert collections of Markdown files (one per chapter) into an epub file that I can load into my Calibre library and review with calibre-web and the @ig3/zhongwen add-on.

There's plenty of Chinese content on the Internet, including many books and novels available as epub documents or easily converted to epub documents. The @ig3/zhongwen add-on doesn't work directly on all sites. Some don't allow selection of text at all. I also find it convenient to be able to review when I'm offline, which I can do if I have copied the text to make my own epub documents. A little collection that I can review as many times as I need to.

While @ig3/zhongwen is easy and very helpful, its definitions don't always help and miss the broader context. Sometimes, for example, I don't know how to break down a sentence into the correct words and phrases. While it is probably obvious to a Chinese person, I often find that there are several overlapping possibilities. I find Google Translate very helpful for understanding the meaning and structure of whole sentences.

With Google Translate, what I sometimes do is copy in a whole sentence, then split it onto multiple lines in the input box to make sure I am correct about what the words and phrases are.

The MDBG website, from which the dictionary used by @ig3/zhongwen comes, is very helpful, with various lookup functions. 

The stroke diagrams on the MDBG website are great! Though the count of strokes is sometimes nonsense. I think the discrepancies of stroke counts come from MDBG adding up counts of radicals in their traditional forms when the character has them in simplified form, with fewer strokes. Or sometimes simply errors. I try not to get hung up on the stroke counts, but I do try to learn the 'correct' stroke order and direction. Teaching sites for hand writing seem to emphasize stroke order and direction very much.

How to clean greasy scouring pads (Scotch-Brite and equivalent)

 I use scouring pads to clean pots and pans in the kitchen. They are like magnets for oil and grease which gets into them and won't come out with dish detergent. For a long time I was throwing them out when they became greasy but I didn't like the waste.

Today I discovered that a bit of caustic soda / sodium hydroxide removes the oil and grease very quickly and doesn't seem to damage the pads at all, leaving them clean and oil free.

Put a little cold water in a small pot. Add a little caustic soda - maybe half a teaspoon. Put in the pad and heat to a simmer for a few minutes, stirring occasionally.

Be very careful with the caustic soda. It is a powerful chemical that can cause burns. Instructions say to add it to cold water, not hot. When it mixes with water, it produces a lot of heat. Instructions say NEVER ADD WATER TO CAUSTIC SODA. Always add caustic soda to cold water.

Fool that I am, I added the caustic soda to hot water. Only a little at a time. Each time, it boiled up. Good thing it was only a little. I'll start with cold water next time.

But almost immediately, I could see the oil and grease coming out of the pad. The water went dark with it. I left it to simmer for maybe 5 minutes, stirring it around a few times. Then I rinsed it out with lots of cold water before touching it.

Caustic soda is a strong base. It can burn you. Handle it carefully. Don't get it on your skin. Don't get water with too much caustic soda on your skin. Definitely don't let it splash in your eyes. Rinse with plenty of water.

 


Thursday, September 29, 2022

Flush node's process.stdin

I wrote an interactive program. It uses read to prompt the user.

But it suffered from type ahead buffering, particularly at a prompt for confirmation to proceed. An accidental extra <CR> entered before the prompt, while waiting for some slow processing to occur, would be processed after the prompt and select the default, which wasn't good whether the default was to confirm or reject.

I needed a way to ensure that only something entered after the prompt would be accepted as a response to the prompt. In other words: defeat the type ahead - flush / purge the stdin buffer before prompting for the confirmation.

For other prompts the type ahead was fine. A set of routine prompts that the user might become very familiar with and type ahead, knowing what they want, despite the slow processing. But not so good for the confirmation.

After much searching I couldn't find a simple way to flush the input buffer.

So, I did it the hard way:

function flushStdin () {
  return new Promise((resolve, reject) => {
    let n = 0;
    const interval = setInterval(() => {
      const chunk = process.stdin.read();
      if (chunk === null) {
        if (++n > 3) {
          clearInterval(interval);
          resolve();
        }
      } else {
        n = 0;
      }
    }, 0);
  });
}

It probably isn't the best way, but it is the best way I have found so far and it seems to work OK. It's human interaction, speed isn't a great concern.

 I have tried many variations. Most surprising was this:

function flushStdin () {
  return new Promise((resolve, reject) => {
    process.stdin.resume();
    setTimeout(() => {
      resolve();
    }, 10);
  });
}

This works somewhat, except that it takes time to flush all the input so with a timeout of 0 it doesn't work or with no timeout: calling resolve synchronously, in the same phase of node's event loop. But wait long enough and all the input will be flushed. The problem being that the time required to flush it all is indeterminate: it depends on how much input is buffered. And there is no way to check how much data is in the buffer.

While I don't find documentation of it and I haven't read the node source code to find out what it actually does, my guess, based on observations is:

The Linux terminal driver is buffering input. Absent a pending read, the terminal driver will buffer some amount of data. I don't know how much. There must be a limit. Eventually the input must be blocked.

It appears node gets data from the terminal driver in line mode: it reads one line of input and buffers it. So process.stdin.readableLength never sees more than the length of one line of input plus the terminating linefeed. Node doesn't fetch the next line from the terminal driver until the current line has been read.

So, even if there are several lines of input buffered by the terminal driver, it takes several iterations of reading a line and processing the line before the buffer is drained and I have found no interface in node for inspecting how much data is in the terminal driver buffer.

It seems odd to me that resuming the input without a data event listener or anything else to read the input actually flushes the input, given sufficient time, but doesn't interfere with subsequent reads (e.g. by readline). Yet it does.

The call to resume causes a resume event to be emitted, followed by a series of readable and data events: one pair of events for each line of input and, contrary to my understanding of what the documentation says, these events are emitted despite there being no listeners for them. Effectively, the buffer is cleared and the data discarded even though nothing read the data. But it takes time.

If you too are curious, you might try this:

const oldEmitter = process.stdin.emit;

process.stdin.emit = function () {
  const emitArgs = arguments;
  console.log(Date.now(), 'emit: ', arguments);
  oldEmitter.apply(process.stdin, arguments);
};

This lets you observe the events emitted from process.stdin. It must interfere with them somewhat. It takes time to write to the console. But my understanding is that it is synchronous: at least, the output is written to an output buffer synchronously, even if it doesn't immediately appear on the display (e.g. if the display is connected by a low speed tty).

But this is mostly speculation: deductions based on observations of various tests using various methods and properties of process.stdin and, via the read package, I think the readline interface.

Node documentation says two seemingly inconsistent things about process.stding when connected to a TTY:

In TTY it says:

When Node.js detects that it is being run with a text terminal ("TTY") attached, process.stdin will, by default, be initialized as an instance of tty.ReadStream and both process.stdout and process.stderr will, by default, be instances of tty.WriteStream. The preferred method of determining whether Node.js is being run within a TTY context is to check that the value of the process.stdout.isTTY property is true.

But in process.stdin it says:

The process.stdin property returns a stream connected to stdin (fd 0). It is a net.Socket (which is a Duplex stream) unless fd 0 refers to a file, in which case it is a Readable stream.

So, which is it? Is it an instance of tty.ReadStream or an instance of net.Socket? Or do they deem that the returned object is at the same time an instance of both? Is a tty.ReadStream an instance of net.Socket?

 

Wednesday, June 15, 2022

Linux duplex scanning

I have been using XSane for scanning but it seems not to have an option for duplex batch scanning.

I found this gist. It didn't quite work but I adapted it to work with my scanner:

#!/bin/bash
scanimage -d 'brother4:net1;dev0' --batch --batch-double
read -p "Flip papers in the feeder, last page on top, press Enter..."
endpage=$(echo "$(ls *.pnm -1 | wc -l) * 2" | bc)
scanimage -d 'brother4:net1;dev0' --batch --batch-increment -2 --batch-start $endpage
for file in *.pnm; do convert $file $file.jpg; done
rm *.pnm
convert $(ls *.jpg -1v | paste) -compress jpeg -page A4 output.pdf
rm *.jpg
 

This works, but the scanner device is specific to my setup. To find the scanner I ran: scanimage -L

Otherwise, it would be better if it created a temporary directory, put all the files in it then copied only the output file back to current directory and deleted the temporary directory. But in the meantime, this works and is easier than struggling with the X(In-)Sane UI.

I had to change ImageMagick policy to allow writing the PDF using convert: in /etc/ImageMagick-6/policy.xml I change "none" to "write" in the line:   <policy domain="coder" rights="write" pattern="PDF" />

This allows 'convert' (and presumably other commands of the ImageMagic set) to write PDF files but not to read them. Reading them is a security concern but writing them should be safe enough.

Friday, June 3, 2022

Calibre-Web epub url churn

I am using calibre-web to view epub documents, with Firefox plugin zhongwen for lookup of Chinese characters.

The zhongwen plugin selects text matching dictionary entries as the cursor moves over the document and opens a popup to display the matching definitions.

The published zhongwen plugin doesn't work with epub documents displayed by calibre-web because it doesn't handle the iframe that the epub document appears in, so I am using a modified version of the plugin: ig3/zhongwen.

This modified version of the plugin works. I can view Chinese epub documents with lookup of the Chinese characters, which is very helpful for my Chinese study.

However, every time the zhongwen plugin changes the selected text, the epub reader updates the URL with a new epubcfi that specifies the selected text. This churn of the URL results in many browser history entries. When I look for something in my browser history, I have to scroll over thousands of these history entries to find what I want.

I don't was a browser history entry every time zhongwen selects text.

With a bit of poking about in the calibre-web source, I found that it uses the Futurepress reader and epub.js libraries to render epub documents.

The epub.js library emits events when selection changes but doesn't have code that would update browser history.

The reader does have code that updates browser history on selection change.

Looking at ~/.local/lib/python3.7/site-packages/calibreweb/cps/static/js/libs/reader.min.js I see "history:!0", which appears to be setting default value of the "history" option which determines if history is updated.

I changed "history:!0" to "history:!1", reloaded the epub document and now the URL is not changed when zhongwen plugin selects text.

But now there are no updates to the URL at all: not even when I move to a different page. So I have gone from too much history to too little.

So, I grabbed a copy of the non-minified reader and edited the function that updates the URL to:

PUBJS.Reader.prototype.selectedRange = function(cfiRange){
  var cfiFragment = "#"+cfiRange;

  // Update the History Location
  if(this.settings.history &&
      window.location.hash != cfiFragment &&
    cfiFragment.indexOf(',') === -1
  ) {
    // Add CFI fragment to the history
    history.pushState({}, '', cfiFragment);
    this.currentLocationCfi = cfiRange;
  }
};

The addition is "cfiFragment.indexOf(',') === -1", which prevents the update if the fragment includes a comma, which it does if it is a "RANGE" of text.

With this change, I get URL updates / history when I use the page navigation but no URL updates / history when text is selected. 

This seems a reasonable compromise and, thus far, everything works. The change hasn't broken anything that I have noticed.

 

Thursday, May 26, 2022

Janne Schra,

The world is full of hard working, talented people who don't get the attention they deserve. Meanwhile, there are Tik Tok and OnlyFans taking people's time and money for trash.

Just one example: I was listening to an old music selection today: Janne Schra: Different. I enjoyed it so I looked her up and listened to / watched several of her YouTube videos. I enjoyed them all. A lot of work went into them - a lot of talent. Yet the had only a few hundred views each.

She collaborated with M. Ward. I liked most of what he posted to YouTube also. They too had almost no views.

YouTube's algorithms work. Alphabet makes a lot of money. But they don't promote what is good. They merely monetize us and our reactions to triggers: titles and thumbnails.

Friday, May 20, 2022

Greg Ellis - The Respondent

I just watched this livestream discussion with Greg Ellis:

 
He speaks calmly but persuasively. 

See his website: The Respondent

And his YouTube channel: The Respondent


Labels