Goodbye Kisumu

Early morning Lake Victoria boat ride

After two years of (mostly) staying put in Kisumu, I’m getting back on the road!

Of all the places in the world, I would not have guessed that I’d break my nomadic streak for life in Kisumu. I’m glad to have spent all this time in the slow pace of a small city - definitely dodged the intense lockdowns around the world.

Things I’ll miss from my life in Kisumu

  • walking down the quiet Okore Road or Ridoch Road as the sun sets and the heat of the day lets up

  • cycling or running along the Lake Victoria shore in the direction of Dunga beach, noticing the water hyacinths surrendering or gaining turf as the “seasons” change.

  • grabbing a booth and working from Java House at West End Mall in the afternoons - Going to Java was the one COVID risk I allowed myself for the sake of keeping my sanity and having a reason to get out of the apartment. Debatable if I held on to my sanity, but it became such a comfort that I’d visit Java at least once a week, and get one of the few vegan/vegatarian options in the menu, so much so that the waiters knew exactly what I’d get. “Green smoothie and a garden salad?”. All I had to do was nod.

  • the stunning views of the hills around Kisumu - I was lucky to be in a fifth floor apartment, and seeing the orange and purple hues of dawn over the hills was an experience.

  • the one-hour (minimum) wait times at most restaurants and how it teaches you to embrace Kisumu’s quirks and work with it - I’d call in my order an hour in advance and would have to wait only 5 - 10 minutes when I got there 😛

  • rushing to Chandarana/Food Plus before closing time for groceries that cannot be found elsewhere in Kisumu (hummus, kombucha, vegan pesto/ice cream/wine, fresh tofu)

  • being accosted by the same group of boda drivers outside West End Mall who know exactly where to drop you off, all jostling to be the one to make 50 shillings. I’ve been told that real Kisumu folks pay only 20 shillings to go to most places, but 50 is the minimum price for foreigners.

  • the long rains - the howling winds, the lightning storms over the lake, the sheer intensity of rain.

  • the dodgy instant electric heaters - Can you really say you’ve lived life on the edge if you haven’t showered under an electric showerhead that instantly heats water, as you contemplate high-voltage electrocution and bodies charred beyond recognition? Just kidding, this probably belongs in the next section.

  • the elaborate greetings. As someone familiar and very comfortable in the brusque, utilitarian, seemingly rude (by Western standards, not mine) ways of urban Indian customer service at grocery stores, and small shops and restaurants, the social ritual of greeting absolutely everyone before engaging with them initially drove me crazy (So inefficient). This is a real transcript of a phone call from an unknown number

    Me: "Hello?"
    Person: "Hello, how are you?"
    Me: "I'm fine, how are you?"
    Person: "I'm fine too"
    *Awkward pause
    Me: "Yes?"
    Person: "I'm calling you from DHL. There is a package at our office...."
    

    But now? Kisumu has done a number on me. I was in India briefly last year, and I got strange looks because I started all interactions with “Hi, how are you?”

  • making small talk with a stranger and discovering that they run a “ministry” on weekends and collections have been down in recent months. The odds that that has happened to me 2 - 3 times!

  • watching the sunset in the weirdly segregationist, melanin-free environs of Dunga Hill Camp on a Friday night, and greeting folks whom you’ve only ever seen with a bottle of beer in their hands (and presumably not their first for the evening). Alcoholism is a real thing, Kisumu!

  • taking a boda to the airport, and catching your flight even when you get to the airport 15 minutes before takeoff.

View of the hills near Kibos Road, Kisumu

Things I won’t miss

  • The garbage burning and the filthy air (sometimes)! Don’t get me started (I’ve written about it before).
  • The drunk-driving! If you see someone driving past midnight on a Friday, I promise you they’re not sober. It blew my mind when I learnt that drunk-driving was just the norm in Kisumu.
  • Maybe it’s just me and my complete lack of social media and/or social skills, but it felt like there was absolutely nothing to do in Kisumu but to go to church or drink to get hammered. At this point in my life (and, I hope, always), I don’t need someone shouting at me that “the fiery depths of hell await you for your sinful ways” in the hope of scaring me into committing to a tithe, nor do I want to run away from an existential crisis by numbing my mind with alcohol (I prefer travelling, i.e, literally running away, instead). That meant..spending a lot of time reading and watching stuff online. And resentfully finding meetup.com and Alliance Française events in Nairobi
    • That said, I’m leaving with a tinge of regret that I didn’t use my time in Kisumu to travel to the places around it. I’ve been learning more about permaculture and natural farming lately, and after spending days trying to find a permaculture farm accessible to me that would be willing to give me a tour, I found one an hour’s drive away from Kisumu 🤦. I also don’t feel like I’ve seen much of Kakamega and beyond, not to mention Mount Elgon National Park!
  • The madenning chorus of church loudspeakers on the weekends, starting as early as 8AM and stretching all the way to about 6PM.

Exciting times for Kisumu

I’m cautiously optimistic about all the upcoming changes in Kisumu - A waterfront promenade is under construction, the Kisumu port is being revived, many parts of the city have been spruced up and new bicycle lanes created in preparation for the Africities summit in a few months, and the colonial-era train is finally functional again, if your definition of functional accommodates a train that averages about 20 km/h and takes 12 hours from Nairobi to Kisumu.

Depending on how the election in August ~is decided~ goes, the next President may have strong ties to Kisumu - Hopefully that translates to a greater focus on the region, if only for the purposes of voter appeasement.

What’s next?

I’m looking forward to doing a bit of travelling - Yes, it’s inconvenient to look up COVID restrictions and take PCR tests, but after a long time in Kisumu, the scale has tipped in favour of travelling despite the inconveniences. I feel silly that I haven’t seen much of east Africa in all this time in the region, so I have Rwanda on my mind.

What I’d really like from this year is to find a small house on a permaculture farm, in a part of the world with a mild climate, spending hours outdoors everyday, working on the farm alongside people willing to teach me their farming practices and put up with my remote work, somewhere I can go cycling or wandering off every weekend.

Larynx - A viable Linux TTS

I spent some time over the weekend experimenting with voice2json and rhasspy, trying to set up a fully offline voice assistant system using Mozilla DeepSpeech for speech recognition, a template file containing all known phrases and their mappings to intents, an intent recognizer and a local shell script to parse the recognized intent and invoke commands (think opening a website or folder when an intent is recognized). Rhasspy was easy to use and really fun. It’s amazing how far we’ve come in terms of open-source tools in the TTS/speech recognition space.

Along the way, I discovered Larynx, a TTS system for Linux with high-quality voices from Glow-TTS and others, with intonations that sound human. I’ve kept an eye on the Linux TTS space for years and have been disappointed by the limited consumer-use options. Often, the pre-trained TTS voices sound all too robotic for everyday use. I suppose that’s understandable given the dearth of open-source voice datasets (which is why projects like Mozilla CommonVoice are so exciting!). It’s nice to have a pleasant pre-trained TTS model natively available on Linux.

My use case is to copy text in a browser/Thunderbird RSS article, hit a shortcut and have the TTS system read the selected text aloud so I can look away from the screen and just listen.

Setup

I followed the Debian installation instructions, and downloaded and installed the tts, lang-en_us and Harvard Glow TTS files.

# cd Downloads # (or /path/to/downloaded/deb/files)
sudo apt install ./larynx*.deb

TTS Shortcut

To create a shortcut that invokes Larynx on selected text, I added aliases in my ~/.bash_aliases file. They use xclip to access clipboard and selection data. On Debian-based systems, you should be able to install it with sudo apt install xclip.

# Speak text passed as argument
# Usage: speak "This is a test"
alias speak="larynx --voice harvard-glow_tts --interactive"

# Speak clipboard text
# Usage: speak-clipboard
alias speak-clipboard="xclip -out -selection clipboard | speak"

# Speak currently selected text
# Usage: speak-selection
alias speak-selection="xclip -out -selection primary | speak"

Under Settings –> Keyboard on GNOME, I added a custom keybinding for Super+S to invoke bash -i -c "speak-selection". This lets me select any text and hit Super+S to invoke larynx

References

Jodi Sudoku

Happy to share that I’m working on Jodi Sudoku, an open-source (AGPLv3), Sudoku progressive web app with the goal of implementing multiplayer support using WebRTC!

I’ve been following the adoption of WebRTC (now supported on all major platforms except Safari, infamous for dragging its feet) and the decentralized web for years, but haven’t had the opportunity to work with it, so I decided to use it in a hobby project.

Frontend

The frontend code uses the standard React + Typescript setup, with React Router and Redux. So far, the web app features

  • Starting a new game with a level of difficulty of your choice
  • Keyboard and mouse/touch input
    • Changing the mode of entering values into cells - Choose a digit first, and then click a cell to enter the digit, or vice versa
  • Responsive cross-platform layout, tested on Firefox and Chrome (desktop and mobile)
  • Undo and redo using redux store history

I’m a big fan of internationalization and supporting regional languages, so the basic one-player implementation currently supports Kannada, English and Polish using react-i18n.

Backend

The backend is more esoteric - I’m interested in using Rails + WebSockets. While I primarily wear the Android hat at work, our backend is built with Rails (a framework I had not worked with before) and I’m interested in improving my Rails fu through this project and contributing more than just the occasional pull request. Rails has a thin wrapper over WebSockets called Action Cable, and I’m curious to see how it fares compared to Node ( + socket.io), which seems to be the internet’s go-to recommendation for scalable WebSockets. WebSockets is a great way to achieve peer discovery (your browser tab discovering peers and finding a way to establish a direct connection despite NAT traversal), required for WebRTC. If there’s a lot of traffic and Action Cable/Rails struggles under the load, I’m aware I might need to swap out Action Cable for Node someday, but I think the Rails experimentation will be worth it. Besides, Node feels like home turf and it’ll be fun to try something different :)

Multiplayer

What’s with the idea of multiplayer Sudoku, you ask? Is there any interest in multiplayer Sudoku? I’m not sure - I found a few multiplayer games on different app stores, but I don’t think there’s a big community. This project is honestly just an excuse to find a way to play Sudoku with my mum - Playing Sudoku together is a ritual whenever I’m in Bangalore. With mugs of basil and ginger tea after dinner, and armed with pens at the kitchen table, we pair up (Jodi (ಜೋಡಿ) is the Kannada word for “pair”) on solving the Sudoku puzzle in the daily newspaper (remember those?), trying to get as many digits as possible but also explaining to each other how we “unlocked” the right digits. I miss that, and I see no reason why that should stop when I’m not in Bangalore :) I’d be happy if others find it useful as well.

The idea is to implement URL-based discovery of peers. Like Jitsi calls, users who open the same link will be able to connect to the server using WebSockets, receive information from the server on the users in the “room”, and then establish browser-to-browser WebRTC peer connections, making the server connection theoretically unnecessary after that point.

I’m thinking of building different multiplayer modes

  • Cooperative: all players in a room collaboratively solve the same puzzle together
  • Challenge (same puzzle): all players in a room individually solve the same puzzle, only aware of the number of empty/filled cells of other players.
  • Challenge (puzzle of same difficulty): All players in a room solve different puzzles of the same difficulty level, only aware of the number of empty/filled cells of other players. Could be fun to also add a “Peek player’s board” feature
  • Time challenge? Turn-based games? Some dastardly variant of Sudoku?

Q: Privacy and fault tolerance

I’ve encountered a lot of flaky internet connections and I’d like Jodi Sudoku to be tolerant of and handle edge cases related to flaky connections, users having to refresh their tabs, abrupt drop-offs etc. Given that exploring WebRTC and peer-to-peer systems is one of the goals, it’s imperative that the server know as little as possible about users, rooms or the type and state of the game in a room.

If the server knows nothing about the state of the game in a room, who is the source of truth in case of failure? Should all players in a room store the game state locally (in the browser local/session storage), and send the most recent state (via WebRTC) to any player who re/joins a room? What happens if two players disagree on the most recent state of the game? Should rooms have a leader who is responsible for storing the game state and sharing it with users who enter the room? What happens if the leader’s connection goes down? I have struggled to square the two seemingly contradictory goals of privacy/decentralization, and maintaining a source of truth for fault recovery, and decided to move forward with making the server aware of users, rooms, and game states. Please reach out if you have any suggestions that would keep the server out of the loop after peer discovery.

My current approach is:

Peer discovery

  1. Abbi enters a room (a room is identified by the link, i.e, judisudoku.app/some-room)
  2. Abbi establishes a WebSocket connection with the server.
  3. Abbi receives the list of users in the room via WebSocket. The list does not have anyone except Abbi. The WebSocket connection with the server is kept open.
  4. Ilana enters the room, connects to the server and receives the list of users. The WebSocket connection with the server is kept open.
  5. Abbi and Ilana use a STUN server, bypass their NAT, and establish a peer-to-peer connection.

Gameplay

  1. Abbi and Ilana begin a cooperative game, notifying each other via WebRTC and notifying the server via WebSocket.
  2. Abbi and Ilana send messages directly to each other via WebRTC when they enter values into cells. They would also need to passively notify the server of the state changes via WebSocket
  3. The server passively stores the game state, doing nothing with it.

New player/rejoin

  1. Jaime joins the game late, and is announced to the other users by the server (the updated list of users is shared) via WebSocket
  2. The server shares the latest state of the game to Jaime via WebSocket
  3. Once connected to the other users via WebRTC p2p connections, Jaime follows step 7 to participate in the game.

Using the server as the single source of truth allows users who join a game midway or experience connection issues and rejoin the game to continue the (collaborative) game from the latest state (if other users have made changes). Other multiplayer modes may need to handle failure/rejoin scenarios differently.

Next steps

I realise I’ve defined a lot of lofty technical ambitions for a simple Sudoku webapp, and I’d like to take this one step at a time. I’m planning to make small releases to keep up my motivation and get feedback from my target demographic of one - When it comes to hobby projects, I tend to be very excited during the design phase and in the early days of implementation, and lose interest and move on to the next shiny puzzle that needs solving when the problem is more or less “solved” in my head :)

The next release will have peer discovery and cooperative game mode.

European fantasy

For the first time in ten years, I marked one whole year (and counting) living in the same country thanks to Covid, and while I’ve enjoyed liberties in Kenya (open restaurants, only one major lockdown and few restrictions, few cases until recently) that many around the world are still waiting to get back, I find myself longing to escape to Europe.

Of course, the Europe in my mind is completely untouched by Covid, there are no restrictions, all art spaces are active, you can drink a beer by the river, it’s springtime and sunny, and you can travel as you please, because that’s how I last saw it :)

Trigger warning: Privileged person whining about the hardships of others

The relentlessness of Kenyan inequality can be hard to bear at times - I’ve been trying to explore the streets of Kisumu by bike, venturing into neighbourhoods and streets I don’t usually take. Cycling by the lake wearing my $30 helmet and seeing parts of people’s houses flooded after heavy rains, and kids collecting water from filthy ditches or the lake in distinct yellow jerry cans while someone else washes their motorbike a few metres away makes me want to give up.

You’d think India would’ve inured me to the injustices of poverty and poor infrastructure, but I’ve realised India is quite good at ghettoising its poor, politely tucking them away in long-forgotten parts of towns and cities that are avoided by anyone who can afford to do so, certainly politicians. Or perhaps it’s more accurate to say the wealthy have managed to outspend, gentrify and shut out the poor from “their” neighbourhoods. The segregation seems less stark to me here in Kenya.

A few days after I moved into my apartment, someone rang the doorbell - It was a shy young woman, probably in her late teens. She seemed very uncomfortable and struggled to say, “I would be grateful to you if I could have any work to do in your house - cleaning the floor, and doing your dishes and laundry?”.

What was I to say? It looks like it’s common to have housekeepers in Kenya, but it makes me very uncomfortable, not just because of the risks of catching Covid and the dubious ethics of paying someone to clean up after me, but also I get nervous at the very thought of strangers coming into my private space and having nowhere to hide while they’re around, possibly for hours (The joys of living with anxiety!). With a lot of guilt and fully aware that I might be cutting off an opportunity for her to make a few hundred Kenyan shillings a week, I declined.

Such moral conundrums are an everyday occurrence - Last month, I stepped out to get some bananas from a nearby supermarket, and as I walked past an empty plot, I saw a woman sitting at a small folding table with no shade, selling eggs, fried meat and bananas. A child played in the dirt nearby. The sun was setting after a hot day, and the bananas were well on their way to brown. I felt torn - I had stepped out just to get bananas, and I could support the woman and her child by buying bananas from her knowing that the bananas would go bad within a day, or go to the supermarket and buy less ripe ones. I kept walking (with only a tiny bit of hyperventilating), but it was a tough choice. Everyday, very real tests of Kant’s categorial imperative take their toll. All the bananas I’ve bought since then have been from her though (and the nearly brown bananas must have just been due to a bad day - the bananas have been great since then)! :)

Is it possible to respect your own values and preferences and still find a way to help others that doesn’t involve paying for things or services you don’t need, or simply handing people a wad of cash?

I know it’s pure escapism, but I’m craving a respite from guilt for a bit. It would be a dream to be in a place where there might still be plenty of inequality but not the kind that leads to extreme starvation or deprives people of basic infrastructure or access to healthcare. Where the crunch of a snail on a bike path getting crushed under my bike wheels despite my best efforts to avoid it is the only source of guilt pangs. Where I’m not constantly aware of my privilege.

I never thought I’d think fondly of places where, among many xenophobic incidents, someone called me a terrorist, pointed at my blinking red bike light and made the gesture of a bomb blast :D

An organisation next door stopped burning garbage!

I’ve written about the pollution caused by poor waste management and garbage burning before. That was spurred by an organisation I live near that burnt all kinds of stuff (dry leaves, carton boxes, and sometimes industrial waste I’m sure) everyday! Garbage burning in a nearby compound I was sick of having to keep my windows shut all the time, so I called them one day and reminded them that it was illegal to burn garbage according to NEMA regulations. The person I spoke to claimed ignorance of any garbage burning within their compound, wanted to know how I knew of their (non-existent) burning, was I perhaps mistaken in thinking it was their compound burning garbage and not the next one, and eventually wanted to give me someone else’s phone number so I could talk to them about this issue. I refused, saying it wasn’t any of my business to talk to someone else, and that I just wanted to remind them that they shouldn’t be exposing everyone around them to fumes everyday. We exchanged phone numbers, and that was it. I wasn’t expecting much from the call, but…it worked! They have since stopped burning garbage. I’m not sure what’s happening to all of it now (I sincerely hope they’re using a garbage service that recycles and/or uses an incinerator and that I haven’t just demonstrated NIMBY behaviour), but there’s one fewer compound I have to worry about as a source of smoke.

I’m surprised that all it took was a phone call! Had I known that before, I wouldn’t have spent weeks psyching myself up to call them, arming myself with facts and preparing responses to imagined questions, not to mention how to put pressure on them (social media? legal notice?) should the call fail to do anything :) Now that I know it can be as simple as making a phone call, I feel unleashed, looking for other opportunities to get organisations, as a start, in Kisumu to do better!

That said, I was somewhat ashamed of myself at one point during the call - The person on the phone wanted to know my phone number (I was passed through to them via the receptionist), and the first thought that came to my mind was “Am I putting myself in danger by sharing my phone number?”. Thanks to mpesa, it’s easy to look up anyone’s full name if you have their Kenyan phone number. Would the organisation find it easier or more cost-effective to shut me up than to deal with the daily garbage burning? I went ahead and gave them my phone number, but I had to remind myself that if it comes to it, I have the resources to deal with any problems the organisation might try to create for me. Made me think of environmentalists who risk their lives to wage year-long campaigns against mining giants and illegal loggers - If a simple thing like calling someone out on garbage burning can make me feel at risk of harm (it’s hard to say if that was paranoia or a legitimate threat), the folks fighting the real battles must be constantly aware of the danger they’re in and yet find a way to put their fears aside and continue to do the work they do ♥