Jacob Vosmaer's blog

AdventureKid Waveforms for the Casio FZ-1

2025-07-29

Like I did with the Yamaha TX16W before, I have converted the AdventureKid Waveforms to work with the Casio FZ-1. In this post I will explain how I did this.

You can download the converted waveforms here.

The AdventureKid Waveforms

The AdventureKid Waveforms are a collection of 4000+ audio files that you can load in your sampler to use it as a synthesizer. This is not what a sampler is typically used for but you can do creative things this way.

The Casio FZ-1 and its wave synthesis mode

The Casio FZ-1 is a sampler keyboard from 1987. Besides being able to play back samples of nearly two minutes in length, it also has a "wave synthesis" mode where you can create single cycle waveforms and use the machine as a synthesizer. This is exactly what the AdventureKid (AKWF) collection is for so it seems very natural to want to use those pre-made waveforms with the FZ-1.

You can't just download the AKWF waveforms onto the FZ-1 because it uses a custom filesystem and file format to store its data. This is more difficult than what I had to deal with with the TX16W because that machine uses AIFF files stored on FAT12 floppy disks. In order to get files onto the FZ-1, I made my own command line utilities that can create FZ-1 disk images and write files onto them. You can read more about that in my last post.

To better understand wave synthesis mode I made a voice with each of its 10 variants (6 "preset waves", "sine synthesis", "cut sample" and "hand drawing") and saved each on a (virtual) floppy disk. I then look at each of the files in hexadecimal format and as audio data.

A "sawtooth" preset wave voice file visualized as audio data A "sawtooth" preset wave voice file visualized as audio data. Note that the first 192 bytes contain sample playback metadata, followed by 832 bytes zero padding. Then at the 1024 byte mark the audio data starts. Because the audio editor interprets the data as 16-bit samples, that is sample offset 512.

Looking at the data as audio (signed 16-bit little endian) made it immediately clear that "wave synthesis" really means sample playback. Looking closer at the playback metadata of the 10 variants, the only difference was in the voice names and the playback start position. I converted the start positions to sample offsets and compared these with the audio data in my audio editor. This told me that the start points in each file were chosen to be zero crossings. For example in the audio data of the "sawtooth" preset wave, the first zero crossing is 96 samples into the waveform, and indeed the playback start position is 60 00 00 00 which is 96 as a 32-bit little-endian integer.

The first zero crossing of the "sawtooth" preset wave The "sawtooth" preset wave of the FZ-1 has its first zero crossing 96 samples into the audio data.

The audio data contains 10 cycles of 192 samples each, followed by a final "cycle" that is missing 64 samples in the middle. I don't know why they did this but my conversion script mimics this layout.

AKWF to FZV conversion script

#!/bin/sh 
set -e

# This script converts a mono AdventureKid Waveform file (600 samples
# long) into an FZ-1 "wave synth" preset.

# defaultvoice prints a binary blob with the default "wave synth" voice
# parameters
defaultvoice() {
  printf '\000\000\000\000\210\007\000\000\000\000\000\000\200\007\000\000'
  printf '\023\000\000\000\000\000\000\000\200\007\000\000\200\007\000\000'
  printf '\200\007\000\000\200\007\000\000\200\007\000\000\200\007\000\000'
  printf '\200\007\000\000\200\007\000\000\200\007\000\000\200\007\000\000'
  printf '\200\007\000\000\200\007\000\000\200\007\000\000\200\007\000\000'
  printf '\200\007\000\000\000\000\000\000\000\000\000\000\000\000\000\000'
  printf '\000\000\000\000\144\000\144\000\144\000\144\000\144\000\144\000'
  printf '\144\000\144\000\000\000\000\000\000\007\177\300\300\300\300\300'
  printf '\300\300\377\000\000\000\000\000\000\000\000\007\177\300\300\300'
  printf '\300\300\300\300\377\000\000\000\000\000\000\000\000\000\200\000'
  printf '\100\000\000\000\000\000\000\000\000\000\000\000\000\000\140\044'
  printf '\110\000%-12.12s\000\000' "$1"
  dd if=/dev/zero bs=832 count=1 status=none
}

if [ $# != 3 ]
then
  echo 'Usage: akwf-fzv IN OUT NAME' 1>&2
  exit 1
fi
in="$1"
out="$2"
name="$3"

tmp=$(mktemp)
trap "rm -- \"$tmp\"" EXIT

# The magic samplerate 14112 ensures that the output file is 192 samples
# long: 44100*192/600 = 14112. We use trim to avoid resampling artefacts
# near the start or end of the audio data.
sox "$in" "$in" "$in" -t s16 -c 1 -L "$tmp" \
  norm -6 rate -v 14112 trim 192s 192s

defaultvoice "$name" > "$out"
# Use cat to string together 10 cycles of the resampled waveform
cat "$tmp" "$tmp" "$tmp" "$tmp" "$tmp" \
    "$tmp" "$tmp" "$tmp" "$tmp" "$tmp" >> "$out"
# Use dd to make the funny last "cycle" which is missing 64 samples
dd if="$tmp" bs=16 count=1 status=none >> "$out"
dd if="$tmp" bs=16 skip=9 status=none >> "$out"

Naming the voices

The FZ-1 lets you name voices with up to 12 ASCII characters. Many of the AKWF files have names longer than 12 characters to I had to come up with new names.

The way I solved this is not entirely consistent; I just looked at each naming pattern that was too long and I rewrote it to fit in 12 characters while preserving uniqueness. Often this meant changing a 0023 suffix to 23 in case the largest suffix was less than 100. Sometimes I removed an _ from a name or I mangled a word. The important lesson I learned about this from my TX16W AKWF adventure is that I need to script the name mangling because I always end up having to convert the files more than once.

You can see how I did it this time in the script below. Note that I used my favorite parallelization trick of generating an ad-hoc Makefile and running make -j 16 on it.

Batch conversion and renaming script

#!/bin/sh
set -e
# This script runs in the root of the AKWF repo
fzdir=AKWF--Casio-FZ-1
rm -rf $fzdir
cp -r AKWF $fzdir # Copy original wav files
rm -rf $fzdir/AKWF_stereo # The FZ-1 is mono
printf 'all:' > fz1.mk
:> fz1.mk.2
find $fzdir -name '*.wav' | while read w
do
  name=${w%.wav}
  name=${name#$fzdir/}
  name=${name#*/}
  case $name in
  AKWF_[0-9]*)
    # Leave AKWF_ prefix intact on AKWF_0001.wav etc.
  ;;
  *)
    name=${name#AKWF_}
  ;;
  esac
  # A long list of special cases to squeeze all names in 12 characters
  case $name in
  sinharm_*)
    name=sinharm_${name#sinharm_00}
  ;;
  oscchip_*)
    name=oscchip_${name#oscchip_0}
  ;;
  R_sym_*)
    name=R${name#R_}
  ;;
  R_asym_saw_*)
    name=Rasym_saw${name#R_asym_saw_}
  ;;
  symetric_*)
    name=symetric_${name#symetric_00}
  ;;
  eguitar_*)
    name=eguitar_${name#eguitar_00}
  ;;
  theremin_*)
    name=theremin_${name#theremin_00}
  ;;
  tannerin_*)
    name=tannerin_${name#tannerin_000}
  ;;
  pluckalgo_*)
    name=pluckalgo_${name#pluckalgo_000}
  ;;
  overtone_*)
    name=overtone_${name#overtone_00}
  ;;
  distorted_*)
    name=distorted${name#distorted_00}
  ;;
  blended_*)
    name=blended_${name#blended_00}
  ;;
  aguitar_*)
    name=aguitar_${name#aguitar_00}
  ;;
  altosax_*)
    name=altosax_${name#altosax_00}
  ;;
  fmsynth_*)
    name=fmsynth_${name#fmsynth_0}
  ;;
  granular_*)
    name=granular_${name#granular_00}
  ;;
  bitreduced_*)
    name=bitreduce${name#bitreduced_00}
  ;;
  clavinet_*)
    name=clavinet_${name#clavinet_00}
  ;;
  clarinett_*)
    name=clarinett_${name#clarinett_00}
  ;;
  esac
  fzv=${w%wav}fzv
  # Add current file to 'all: ' statement
  printf ' %s' $fzv >> fz1.mk
  # Add command to render current file
  printf '%s:\n\t../jv/fz1/akwf-fzv %s %s %s\n\trm %s\n' \
    $fzv $w $fzv $name $w >> fz1.mk.2
done
echo >> fz1.mk
cat fz1.mk.2 >> fz1.mk
make -j 16 -f fz1.mk

Bundling up the converted voice files

At first I thought I was done when I knew how to convert a single AKWF file to a corresponding FZ-1 voice. But I then found out that unlike the TX16W with Typhoon, where you can load all the files on a floppy at once, with the FZ-1 you have to load voice files one by one. This is tedious because you click in the menu, load one file, wait 1 second and hear if you like the sound. If not then repeat.

To avoid this tedium the FZ-1 has a function where it can store and load its entire memory contents from disk in one go. The FZ-1 can only hold 64 samples in memory at a time and some of the AKWF sub-directories have more than 64 files in them so I had to come up with a way to divide the 4000+ files in the collection into sets of 64 or less and turn those sets into FZ "full data dumps" (also known as FZF files).

To make this work with my Gotek floppy emulator, I then also had to put the FZF files on floppy images. The FZ-1 does not allow you to have more than one FZF file on a floppy, or rather if you have more than one FZF, it does not let you choose between them and always loads the first one. So each FZF file has to be wrapped in its own disk image. This wastes a lot of space because an FZF file with 64 AKWF voices is 272KB large and an FZ-1 disk image is always 1280KB. Luckily the total size of the collection including disk image overhead is only 124MB which fits easily on the USB stick of my Gotek drive.

I used an Awk script to split each AKWF subdirectory into FZF bundles of 64 voice files. This means that some FZF files have fewer than 64 voices in them but I felt that here it was better to be more predictable rather than to minimize the number of FZF files.

Bundling the FZV files into FZF files

#!/bin/sh
set -e
fzdir=AKWF--Casio-FZ-1
outdir=$fzdir
mkdir -p $outdir/img $outdir/fzf
find $fzdir -type d -mindepth 1 | sort | while read dir
do
  name=${dir#$fzdir/}
  find $dir -name '*.fzv' | sort | awk -v name=$name -v outdir=$outdir '
    BEGIN { if(name ~ /AKWF_[^0-9]/) name=substr(name, 6, length(name)-5) }
    { voices = voices " " $0 }
    NR%64 == 0 { command(voices); voices="" }
    END { if(NR%64 > 0) command(voices) }
    func command(voices,    id) {
      # For the directory AKWF_hvoice, we get id hvoice-1 and hvoice-2
      id=name "-" int((NR+63)/64)
      # Print long command that packs up to 64 voices into one FZF
      printf "../jv/fz1/fzbuildfull %s/fzf/%s.fzf %s\n", outdir, id, voices
      # Create new disk image
      printf "../jv/fz1/fzformat %s %s/img/%s.img\n", id, outdir, id
      # Put the new FZF file on its disk image
      printf "../jv/fz1/fzputfile %s/img/%s.img 0 %s/fzf/%s.fzf\n",
        outdir, id, outdir, id
    }
  ' | /bin/sh -e
done

Conclusion

And that was it, I hope. When I did this for the TX16W I learned that over time issues may come up that prompt me to re-convert the files. I have automated the process so that should not cost much time. And if you want to do it yourself then now you know how to.

I hope this was interesting, and if you have an FZ-1 then I hope you will enjoy trying out the AKWF waveforms. Thanks for reading!

Tags: music casio fz-1 c akwf

IndexContact