More from Paolo Amoroso's Journal
<![CDATA[My journey to using and programming computers started around 1983 with a Sinclair ZX Spectrum 48K, the very first I owned. At the time I was clueless. For example, I wondered whether, once I loaded a program from tape, I needed to write it back at the end of the session so that it didn't vanish when turning off the machine. The Sinclair ZX Spectrum Introduction and the Sinclair ZX Spectrum BASIC programming guides that came with the device were my first learning resources, complemented by the great Italian computer magazine MC-microcomputer. Aside from some books, in that pre-online era technical documentation wasn't easy to come by in Italy, especially foreign works in English. I pored over the Spectrum manuals, reread them many times, and experimented with the sample code. I've never been into gaming but did run many games to see what the machine could do. I've come a long way since then, hopefully. #retrocomputing #personal a href="https://remark.as/p/journal.paoloamoroso.com/the-origins-of-my-computing-journey"Discuss.../a Email | Reply @[email protected] !--emailsub--]]>
<![CDATA[GravityLoops, my gravity simulator in Interlisp and LOOPS, can finally show something on the screen. The program now animates a body of mass like the Moon interacting under gravity with a body of mass like the Earth. The dots in the simulation window here are the bodies after 180 days of simulated time, with the Earth at left. Watch the full run. Screenshot of a still frame of a simulation of a body like the Earth and one like the Moon interacting under the muatal gravity. Well, that's not much. But the code confirms the simulation loop with an offscreen buffer works well. After putting in place some infrastructure, to get there I wrote the simulation loop, tweaked a few methods, wrote a demo function that sets up the simulation, and fixed a few bugs. There's a lot more to do. I need to make the graphics of the body markers more complete and explanatory, control the speed of the animation, fix more bugs, and refactor to decouple some interclass dependencies. And, of course, GravityLoops will also have a user interface to control the simulation and enter the parameters. #GravityLoops #Interlisp #Lisp a href="https://remark.as/p/journal.paoloamoroso.com/early-simulations-with-gravityloops"Discuss.../a Email | Reply @[email protected] !--emailsub--]]>
<![CDATA[I started working on the class Universe of GravityLoops, my gravity simulator in Interlisp and LOOPS. I defined the class itself and the main methods, Universe.Register and Universe.Simulate. The class represents a collection of bodies and manages the parameters and state of the simulation. Universe.Register adds a body to a universe, Universe.Simulate runs the simulation. In the C++ code of the article my design draws inspiration from, an instance variable of the class UNIVERSE holds a pool of bodies in an array, with the most recently added body indexed by another instance variable. In GravityLoops the corresponding instance variable bodyPool is a list which, as the article notes, is more versatile and doesn't need the index. Universe.Simulate, just a stub for now, is the core method. It will update the state of the simulation, display the bodies in a graphical window along with status information, and check whether the user interrupts the simulation. The C++ program runs the simulation until the user presses a specific key and GravityLoops will have a similar feature. I'll also have the program accept a number of time ticks to step the simulation through. For Universe.Simulate I'll mostly follow the C++ code. But I plan to revisit the decision after I have something running to experiment with. I may want to split the simulation functionality into more than one method to separate the simulation itself from output, or redesign control around LOOPS' active values. #GravityLoops #Interlisp #Lisp a href="https://remark.as/p/journal.paoloamoroso.com/representing-the-universe-of-gravityloops"Discuss.../a Email | Reply @[email protected] !--emailsub--]]>
<![CDATA[I started working on GravityLoops, a software that simulates a collection of bodies interacting under the mutual gravity. I develop it on Medley in Interlisp and its object extension LOOPS, the Lisp Object-Oriented Programming System. GravityLoops will show an animation of the bodies and their motions, along with facilities for defining the parameters of the system and controlling the simulation. Motivation I've been meaning to do a LOOPS learning project but none of the ideas I initially came up with clicked. I wanted something more complex than a toy but easy enough to implement with reasonable effort. The project should also incorporate naturally the features of LOOPS, such as the gauges library of graphical meters and dials for displaying quantities. I finally stumbled upon the gravity simulator described in the article Force-Based Simulations by Todd King in the September, 1989 issue of Dr. Dobbs Journal. It's just perfect. I'm adapting to LOOPS the design of the sample C++ code that comes with the article. It's nice as it reads like an object-oriented domain specific language for simulation. The code is so short and clean I can fully understand it despite my minimal C++. I never thought I would say that of C++. There is much to like of King's program starting from its domain, astronomy and physics, which overlaps with some of my passions. The project is period accurate too as when Dr. Dobb's Journal published the article LOOPS was still under development. And, along with window systems, simulation was among the killer applications object-oriented programming proponents pointed to. The program comprises only two, hierarchically unrelated classes, a shallow inheritance design more in line with the later evolution of object-oriented programming. But the application does offer other potential classes that are a good fit for LOOPS. For example, I plan to specialize the LOOPS class Window to represent the simulaton window. I will likely need more classes for the GUI, such as dialogs for entering the simulation parameters. LOOPS is one of the subsystems best integrated with the Interlisp environment and comes with good documentation. I want to experience this high integration, the ability of combining tools designed to work together that comes natural once you're familiar with the environment. Adapting King's program to the Interlisp environment is also an opportunity to employ useful programming techniques like screen buffering to improve animation fluidity. Plus, anything that draws pretty graphics is fun. Design To adapt Todd's design to LOOPS I create matching classes with similar instance variables and methods, named according to the LOOPS style. I will rename a few confusing methods, such as UNIVERSE::service() to register a body with a universe which I'll call Universe.Register, and UNIVERSE::big_bang() to run the simulation which will become Universe.Simulate. The C++ code represents a 2D vector as a struct that I map to an Interlisp record. A class seems overkill. Todd's program outputs to the MS-DOS text console via the conio library. GravityLoops instead will draw graphics in a window. So far the code implements the Body class that represents a body. I'm about to start working on the Universe class that holds a collection of bodies and manages the parameters and state of the simulation. Once the core classes are in place I will turn to implementing the animated simulation. #GravityLoops #Interlisp #Lisp a href="https://remark.as/p/journal.paoloamoroso.com/gravityloops-a-gravity-simulator-in-interlisp-and-loops"Discuss.../a Email | Reply @[email protected] !--emailsub--]]>
<![CDATA[Insphex adds the Hexdump item to the File Browser menu to view the hex dump of the selected files. The initial implementation called the public API for adding commands at the top level of the menu. To later move the item to the See sumbenu that groups various file viewing commands I resorted to list surgery, as the API doesn't support submenus. The problem is internal system details can and do change, which happened to the File Browser menu and led to an Insphex load error. I fixed the issue by reverting the public API call and now the item is back at the top level of the menu. Insphex is a hex dump tool similar to the Linux command hexdump. I wrote it in Common Lisp on Medley Interlisp. #insphex #CommonLisp #Interlisp #Lisp a href="https://remark.as/p/journal.paoloamoroso.com/rearranging-the-file-browser-menu-for-insphex"Discuss.../a Email | Reply @[email protected] !--emailsub--]]>
More in programming
I listen to a lot of podcasts, and I like how they fit around other tasks. I press play, lock my phone, and put it down. I’m free to wash the dishes, fold the laundry, or shop for groceries. Unfortunately, more and more information is only published as a video. Technical talks, conference sessions, video essays – they don’t work in an audio-only podcast app. I could convert these videos to MP3 files, but that breaks down the moment a video isn’t pure spoken word. If a speaker says, “Look at this slide” or holds up a diagram, an audio-only file leaves me stranded. I don’t want to give up the podcast player I like, nor stare at a screen for an hour – but I do want the information in these videos. To solve this, I’m abusing my podcast player’s chapter support. This gives me the best of both worlds: I can listen to a video as audio-first, and glance at my lock screen if I need a moment of visual context. The idea: Chapters every few seconds MP3 files can have ID3 metadata, and ID3 metadata can include chapters. A chapter covers a particular time range, and it can have an associated title, description, and cover art. My podcast app of choice is Overcast, which can’t play videos, but it does have robust chapter support. I can jump between chapters, navigate a table of contents, and see per-chapter cover art. To get videos into Overcast, I’m creating MP3 files with a new chapter every few seconds, and the per-chapter cover art is a corresponding frame from the video. As I play the file, I get a slow, stop-motion-like rendition of the original video. If my phone is locked, I can glance at my lock screen and see the current frame in the Now Playing screen. Overcast is developed by Marco Arment, and I got this idea from Forecast, his app for adding chapters to podcasts. In particular, I was struck by its ability to create chapters that don’t display in the chapter list – ideal if I don’t want a table of contents with hundreds of entries. As I was developing my script, I compared my output to the output from Forecast to ensure I was creating the chapters correctly. The code: FFmpeg and Mutagen There are three steps in this process: Convert a video file to an MP3 Extract images from the video at a fixed interval Insert the images as hidden chapters in the MP3 file Let’s go through each in turn. 1. Convert a video file to an MP3 Converting a video file to an MP3 is a single FFmpeg command: ffmpeg -i video.mp4 audio.mp3 This is consistently the slowest step of the process, and I do wonder if I could use different settings or an alternative encoder to make it go faster – but it’s not slow enough to be worth further investigation. 2. Extract images from the video at a fixed interval Extracting images from a video needs a more complicated FFmpeg command: ffmpeg -i video.mp4 \ -vf 'fps=1/5,scale=iw*sar:ih,scale=min(iw\,945):min(ih\,945):force_original_aspect_ratio=decrease' \ thumbnail_%04d.jpg This extracts an image every 5 seconds, downscales any image larger than 945 pixels square (while preserving the original aspect ratio), and saves the results as sequentially numbered JPEG images (thumbnail_0001.png, thumbnail_0002.png, and so on). The key is the -vf flag, which defines two FFmpeg filters: The fps filter selects one frame every 5 seconds (fps=1/5). The first scale filter scales the width based on the sample aspect ratio (scale=iw*sar:ih). Without this filter, frames can be stretched and distorted. The second scale filter scales the input video, preserving the original aspect ratio (force_original_aspect_ratio=decrease), and ensuring the output images fit within 945×945px or the size of the input video, whichever is smaller. My limit is 945 pixels because that’s the largest size that cover art is shown on my iPhone. This filter still isn’t completely correct – it sometimes creates images from portrait videos that are smaller than I’m expecting – but it’s good enough. These are only thumbnails for glancing at, and if I want to change it later, I can always do the image resizing outside FFmpeg. 3. Insert the images as hidden chapters in the MP3 file Inserting the chapters into the MP3 file is more complicated. Although FFmpeg has basic support for ID3 metadata, as far as I know, it can’t insert chapters with per-chapter artwork. Instead, I’m going to reach for Python and the Mutagen library. Here’s the code to add a chapter to an MP3 file: from mutagen.id3 import APIC, CHAP, ID3, PictureType audio = ID3("audio.mp3") with open("thumbnail_0001.jpg", "rb") as f: img_data = f.read() image_frame = APIC(mime="image/jpeg", type=PictureType.OTHER, data=img_data) chapter_frame = CHAP( element_id="chp1", start_time=0, end_time=5 * 1000, sub_frames=[image_frame] ) audio.add(chapter_frame) audio.save() This creates a single chapter that lasts the first 5 seconds (0 to 5000 milliseconds), and the per-chapter cover art is thumbnail_0001.jpg. If we ran this in a loop, we could add images for every 5 second slice of the original video. This code is inserting two frames into the ID3 metadata: The CHAP (chapter) frame contains the timing information, and it can have subframes for metadata like title, chapter art, or associated URL. The APIC (attached picture) subframe contains information about a picture, which can either be a blob of image data or a URL to an image on the web. Normally, you’d also insert a CTOC frame which defines a table of contents, but I don’t want a TOC with hundreds of 5-second chapters, so I’m deliberately not doing this here. This is allowed by the ID3 spec – you’re not required to insert a CTOC frame if you’re using chapters, and you can have chapters that aren’t listed in your table of contents. To work out which frames I needed, I used Forecast to create some chapters by hand, and I inspected their frames. In particular, loading an MP3 and calling Mutagen’s pprint() method shows a human-readable list of frames, and then I could drill into the individual fields: from mutagen.id3 import ID3 audio = ID3("audio.mp3") print(audio.pprint()) I wrapped all this code in a project called glancecast, which allows you to convert a video file with a single command, with optional flags to set the frame length and chapter art size: $ python3 glancecast.py interesting_talk.mp4 interesting_talk.mp3 The process takes a minute or so to complete, most of which is spent transcoding the video file to MP3. The resulting MP3s are usually 40 to 50 MB in size, which is very reasonable. The outcome: How it looks in practice Here’s what one of these “glanceable” podcasts looks like in Overcast and on my lock screen: Maggie Appleton presented this talk over two years ago and it’s been on my “talks to watch” list ever since. Once I put it in Overcast? I listened to it in less than a day. It’s not a lot of extra information, but enough that I can quickly glance down and get the gist of what a speaker is saying. Both views update with a new frame every few seconds, or I can put my phone in my pocket and ignore the screen. I’ve used this approach for half a dozen videos so far, and I’m happy with the results. I expect to keep using it, because I have a long queue of videos I’ve been meaning to watch. If you’d like to try this, check out glancecast for the full code and instructions. [If the formatting of this post looks odd in your feed reader, visit the original article]
Andrew Baker, the current Group CIO at Capitec Bank wrote an interesting piece on AI and open source, and how these tools that generate code according to one’s specification may replace the general reliance on open source implementations done by contributors around the world. I’d really recommend reading it. I have great admiration and respectContinue reading "AI Isn’t Replacing Open Source"
A framework for thinking about when AI involvement is additive or a violation
Why we need richer, thicker interfaces and better boundary objects for collaborative planning with agents
A look at 10 foundational pillars that enable agents to operate more competently and more efficiently in any codebase.