DEV Community

Efim Rovaev
Efim Rovaev

Posted on

Retro-programming - Commodore64 music engine built in TASS

Intro

Many developers have been trying to recreate computer entertainment technology from the past. This trend is known as retro-programming. One of the popular ideas of this trend is mimicking the tech first used in Commodore 64. This device still holds the official Guinness World Record as the best selling single computer model in history. Commodore 64, also referred to as C64, scored sales about 30 mln units between 1982 and 1994 according to independent estimates. Commodore 64 is an iconic machine, a great contribution to the gaming, digital music and art industries we know today.
One of the breakthrough technologies that made Commodore 64 so popular was its built-in sound chip, the MOS Technology 6581 SID (Sound Interface Device). It was capable of supporting three sound channels. It might look ridiculous now, but at the time it was a very impressive achievement for a relatively affordable home device. Also, this particular engine was much appreciated for its warm and very typical sound signature. Some of the most iconic game soundtracks of all time were created using SID.
This article provides an insight on Commodore 64's sound capabilities and how they were used to create music. We will then learn to build a Commodore 64 app using assembler in TASS.
This project is based on an open-source GitHub repo: https://github.com/r4dx/vicious

The Icon: C64 Music Engine

Introduced in 1982, the C64 was famous, among other features, for its sound capabilities. Its sound hardware consisted of a SID chip that supported three voices with envelope generators and a filter. This feature enabled creating complex soundscapes and music compositions, so this chip quickly became a long-time favourite among musicians and composers. In those days, computer music was primarily created by programming the SID chip directly in machine language. This process was complex and time-consuming. It also required programming skills in addition to music talent. However, the results were often impressive.
You can find more about the famous chip and its capabilities here:

Modern day: TASS and its capabilities

64tass is an open-source, multi-pass macro assembler for the 65xx processor series. It is written in highly portable C and is aimed at achieving 100% syntax compatibility with Omicron TAS, C64’s native TurboAssembler. The assembler is compatible with all major 6502 CPU variants, including MOS 6502 and 65C02, and supports features like arbitrary-precision integers, Unicode character strings, built-in linker with section support and various memory models. The software is in the phase of active development and is hosted on SourceForge platform (see link below).
Programming a SID chip using TASS, musicians were able to take full advantage of the C64 sound capabilities and create some interesting pieces of music:

To learn more about the TASS macro assembler, you can check out this link: http://tass64.sourceforge.net/

Getting started

Let us install the basic tools to start our retro-programming experiment:

  • To install 64tass, you need to download the latest version from the official website (http://tass64.sourceforge.net/) and extract it to a folder on your computer. Once extracted, you can add the folder to your PATH environment variable so that you can run the 64tass assembler from the command line
  • GTK3VICE Emulator: This is a Commodore 64 emulator that is used to run and test your code. To install GTK3VICE, you need to download the latest version from the official website (https://sourceforge.net/projects/vice-emu/files/releases/binaries/windows/GTK3VICE-3.5-win64.7z/download) and extract it to a folder on your computer. Then, you can add the folder to your PATH environment variable so that you can run the GTK3VICE emulator from the command line.
64tass.exe -a --list=listing.asm --cbm-prg main.asm sid.asm 
if errorlevel 1 exit
x64sc.exe a.out

Enter fullscreen mode Exit fullscreen mode

The make.bat file provided is a simple batch file for compiling and running the code we are writing. Here's a brief explanation:

  • 64tass.exe -a --list=listing.asm --cbm-prg main.asm sid.asm Compiles the main.asm and sid.asm source files into a single Commodore 64 executable file, a.out. The -a option enables the listing output and the --list option specifies the output file name to be listing.asm which is a version where macros are expanded. The --cbm-prg option specifies the output format to be a Commodore 64 program file.
  • x64sc.exe a.out This starts the GTK3VICE emulator and loads the a.out binary, allowing you to listen to the music you have created. With these tools in place, you are now ready to start retro-programming.

Diving into the code

The core code is defined in the sid.asm. Let us have a better understanding of it.
The program defines several macros making it easier to set up the SID chip's audio channels and filter. For example, the setup macro sets up a voice channel to play a specific waveform with a specific set of attack, decay, sustain, and release values. The macro also accepts an optional argument for the pulse wave duty cycle, which sets the duty cycle of the pulse waveform.

setup .macro voice, waveform, attack, decay, sustain, release, pulse_duty_cycle=0
  pha
  voice_reg = VOICE_REGS[\voice]
  .st voice_reg + PulseWaveDutyCycleLo, #(\pulse_duty_cycle & $00ff)
  .st voice_reg + PulseWaveDutyCycleHi, #(\pulse_duty_cycle>>8 & $000f)
  .set_ctr_reg \voice, \waveform
  .st voice_reg + AttackDecay, #(\attack << 4 | \decay)
  .st voice_reg + SustainRelease, #(\sustain << 4 | \release)
  pla
.endm
Enter fullscreen mode Exit fullscreen mode

Please note the usage of st macros - it is employed to store a value in a memory address.

The setup_filter macro sets up the SID chip's filter, which enables filtering specific frequencies from the audio output. This macro takes arguments for the cutoff frequency, resonance, and voice channel mask, which defines what channels will be affected by the filter.

setup_filter .macro cutOffFreq, resonance, v1=false, v2=false, v3=false, hi_pass=false, lo_pass=false, band_pass=false, mute_v3=false
  .set_cutoff \cutOffFreq
  voice_mask := 0
  .if \v1
    voice_mask |= %00000001
  .endif
  .if \v2
    voice_mask |= %00000010
  .endif
  .if \v3
    voice_mask |= %00000100
  .endif
  .st FilterResonanceAndRouting, #(\resonance << 4 | voice_mask)
  mode := 0
  .if \mute_v3
    mode |= %10000000
  .endif
  .if \hi_pass
    mode |= %01000000
  .endif
  .if \band_pass
    mode |= %00100000
  .endif
  .if \lo_pass
    mode |= %00010000
  .endif
  .st FilterModeVolumeControl, #mode
.endm
Enter fullscreen mode Exit fullscreen mode

The encode macro is used to translate musical sheet notation into a series of numerical values that can be played by the SID chip. It takes as input a musical sheet in the form of a string and a default octave. The musical sheet string is then parsed, with each note being converted into a numerical representation based on the octave and the note name.
The encode macro uses the NOTES dictionary to map note names to numerical values. The NOTES dictionary contains the notes from C to B and x which represents a skip note. The pack macro is used to combine the octave and note index into a single numerical value that can be played by the SID chip.
The encode macro is important because it enables the creation of music using a human-readable format, which can then be easily translated into the numerical format required by the SID chip. It provides a convenient way to describe music without having to manually enter numerical values for each note.

NOTES = {"C":0,"C#":1,"Db":1,"D":2, "D#":3, "Eb":3,"E":4,"F":5,"F#":6,"Gb":6,"G":7,"G#":8,"Ab":8,"A":9,"A#":10,"Bb":10,"B":11,"x":12}
SKIP_NOTE = $ff
encode .macro sheet, default_octave
  DELIMITERS = [" "]
  last := ""
  .for i := 0, i < len(\sheet), i += 1
    sym := \sheet[i]
    .if !(sym in DELIMITERS)
    last ..= sym
    .if i != len(\sheet) - 1
        .continue
    .endif
    .endif
    .if len(last) == 0
    .continue
    .endif
    octave := \default_octave
    note_idx := 0
    .if len(last) <= 3
    .if last in NOTES
        note_idx := NOTES[last]
    .else
        octave := last[0] - '0'
        note_idx := NOTES[last[1:]]
    .endif
    .else
    .error "Note should be defined by <= 3 symbols: " .. repr(last)
    .endif
    .if octave > 7 || octave < 0
    .error "Incorrect octave in note: " .. repr(last)    
    .endif
;   .warn repr(last) .. " = octave: " .. repr(octave) .. " note: " .. repr(note_idx)
    .pack octave, note_idx
    last := ""
  .endfor
.endm
Enter fullscreen mode Exit fullscreen mode

This is used in v1, v2 and v3 macros which represent 3 voices (or different tracks so to say) in SID.

v1 .segment sheet, octave=4
  jmp sid__v1_bytes_end
sid__v1_bytes:    
  .encode \sheet, \octave
sid__v1_bytes_end:
.endm
Enter fullscreen mode Exit fullscreen mode

The play_next_uses_xy macro is a macro that is used to play the next note in a sequence of musical notes. The XY in the macro name refers to the X and Y registers.

Conclusion

This is a brief intro into SID programming and a very primitive macro engine to build your music in human-readable format.
Take a look at the main.asm and try to guess which 80s movie OST is it.

Top comments (1)

Collapse
 
nataliagrybovska profile image
Natalia

Nice overview, thank you for covering the topic.