Yamaha SU700

Yamaha SU700

I bought a Yamaha SU700. It's a sample-based groovebox released by Yamaha in 1998. 44.1KHz 16-bit stereo sampling, 43 effects, between 4 and 48MB of memory, and optional expansion cards in addition to to the Floppy drive. A real beast of its time, and arguably just a Yamaha A3000 you can take on the road.

SU700 Photo

Details

There's a lot of buttons and knobs and pads on this thing. We'll get to the knobs later, but the pads are (for those responsible for playing samples) velocity-sensitive, and broken into three groups:

These groups are kind of arbitrary. On one hand, they do quicken making music, as you can slap your drums and bass/synth on the loop pads, a couple of pads on the composed loop pads, and you have something going pretty much instantly. On the other, you can achieve the same thing with the free pads by pressing them at the right time - so it would clearly be better to just have 8 free pads.

There's a slew of buttons in the middle which perform miscellaneous tasks like loading samples, saving projects, and erasing 'events' on tracks (as if you'd never pressed them. I'm still not clear what the scene and marker buttons do. I suppose I need to read the manual. It's plenty fun even without reading it.

The touch-bar-esque device on the right is a "ribbon" that allows you to modulate the value of the left-hand buttons in a way that's more convenient than turning a knob. Though I've mostly only used it to play with cutoff for that 'opening-up' sound.

Finally, the buttons on the left relate to tracks/pads. You press the button, and then use the colourful knobs to tweak the value. Which leads to the first issue:

Rotary Encoders

Yamaha seem real bad at making/picking rotary encoders. The A3000 was shite in that respect too. Fortunately, they can be replaced with some work. I opted to go for the ALPS EC12E2420802, which worked out around 1.30GBP each. However, annoyingly, the encoders used by Yamaha have four legs instead of three, and the arrangement of the legs differs. Which means jumpers are required. I managed to get buy with some 22AWG solid-core I had kicking about. But if I were doing it again, I'd maybe opt for something thinner and use longer bits of wire.

Adding Samples

This is the biggest disadvantage to the SU700. By far. In it's "stock" configuration, you have a floppy drive, and that's it. Unsurprisingly, 1.44Mb doesn't get you very far. The workflow in this configuration is as follows:

  1. On a computer, export your samples as AIFF.
  2. Copy those samples onto a floppy formatted as FAT12.
  3. Insert the floppy and use the "LOAD" button combo to assign it to a track.
  4. Eject the floppy and insert the next one, because you didn't get all of it on one disk.

This works, and is how Yamaha intended you to do it. But we can do one better. Rather than piss-about with actual floppy disks (admitedly, they do have an aesthetic about them that makes me like the idea of having a shelf full of curated samples) - we emulate a floppy disk drive.

GOTEK make floppy-drive emulators. You replace your actual floppy drive with one, and then put your floppy images onto a FAT32/exFAT USB stick. You can then use the front-panel of the GOTEK to select the currently 'inserted' floppy.

This then leads to a question of how you 'get' floppy images. In my case, I used the SU700 to format a real floppy, then used dd to dump it to a file named "BLANK.IMG". I could then mount that file in BSD/Linux/MacOS, copy my samples, and eject it. All good. But tedious. Thus:

autofloppy.rb

#!/usr/bin/env ruby
# Crawl directories and group samples into 1.44Mb directories,
# which are then copied into floppy images.
require 'pry'
require 'fileutils'

# Samples should be stored in the following structure:
#      root/samples/{type}/{collection}/sample_a.aiff
# i.e. root/samples/vocal/junglehitsvol1/sample_a.aiff
#                                        sample_b.aiff
#                                        sample_c.aiff

# This will yield a structure like so:
#      root/floppies/{type}/{collection}/{type}{collection}{n}.img
# i.e. root/floppies/drumloops/jungle_samples/DLJUN001.IMG

# Valid types (defined in SampleLibrary)
# drumloop
# syntloop
# stab
# pad
# fx
# vocal

# SampleLibrary contains SampleBanks (drumloops, stabs, etc)
# SampleBanks contain SampleCollections (drumloops from AKAI Sampler CD #120, #69, etc)
# SampleCollections contain SampleBatches (samples grouped into 1.44MB blocks)

class SampleLibrary
  def initialize(root)
    @root = root
    @floppy_dir = root + "/floppies"
    @blank_floppy = root + "/floppies/BLANK.IMG"
    @blank_floppy_name = "SU700 DISK"
    @samples_dir = root + "/samples"
    @sample_types = {"drumloops"  => "DL",
                     "bassloops"  => "BL",
                     "synthloops" => "SL",
                     "stabs"      => "ST",
                     "pads"       => "PD",
                     "fx"         => "FX",
                     "vocal"      => "VO" }
    @banks = {}
    @sample_types.each do |sample_type, shortcode|
      sample_type_dir = @samples_dir + "/" + sample_type
      next unless Dir.exist?(sample_type_dir)
      @banks[sample_type] = SampleBank.new(sample_type_dir, sample_type)
    end

    @banks.each do |sample_type, bank|
      unless Dir.exist?(@floppy_dir) then Dir.mkdir(@floppy_dir) end
      st_dir = "#{@floppy_dir}/#{sample_type}"
      unless Dir.exist?(st_dir) then Dir.mkdir(st_dir) end

      bank.collections.each do |col|
        col_dir = "#{@floppy_dir}/#{sample_type}/#{col.name}"
        unless Dir.exist?(col_dir) then Dir.mkdir(col_dir) end

        col.batches.each_with_index do |batch, idx|
          floppy_path, floppy_name = long_to_short(sample_type, @sample_types[sample_type], col.name, idx)

          puts "Creating floppy #{floppy_path}"
          FileUtils.copy(@blank_floppy, floppy_path)

          block_path = "/dev/loop16"
          mount_path = "/mnt/fd"
          %x{losetup #{block_path} "#{floppy_path}"}
          %x{mount -t msdos #{block_path} #{mount_path}}
          puts "Mounted floppy #{floppy_name} at #{mount_path} (#{block_path})-- copying."

          batch.files.each do |file|
            file_size = File.size(file)
            new_file_name = file.split("/")[-1].upcase.tr(" ", "").sub(".AIF", "")[0..8].strip + ".AIF"
            puts "Copying #{file} to #{mount_path}/#{new_file_name} (#{file_size} bytes)"
            FileUtils.copy(file, "#{mount_path}/#{new_file_name}")
            %x{rm -rf "#{mount_path}/fseven*"}
          end

          puts "Renaming #{@blank_floppy_name} to #{floppy_name}"
          %x{fatlabel "#{block_path}" "#{floppy_name}"}

          puts "Syncing...."
          %x{sync}

          puts "Dismounting #{mount_path} (#{block_path})"
          %x{umount "#{mount_path}"}
          %x{losetup -D}
        end
      end
    end

  end

  def long_to_short(st_l, st_s, collection, num)
    colname = collection[0..2].upcase
    padnum = num.to_s.rjust(3, '0')
    short_name = "#{st_s}#{colname}#{padnum}"
    return "#{@floppy_dir}/#{st_l}/#{collection}/#{short_name}.IMG", short_name
  end
end

class SampleBank
  attr_reader :path, :sample_type, :collections
  def initialize(bank_dir, stype)
    @path = bank_dir
    @sample_type = stype
    @collections = []
    Dir.children(bank_dir).each do |collection|
      collection_dir = @path + "/" + collection
      @collections << SampleCollection.new(collection_dir, collection)
    end
  end
end

class SampleCollection
  attr_reader :path, :name, :raw_files, :batches
  def initialize(col_dir, col_name)
    @path = col_dir
    @name = col_name
    @raw_files = Dir.children(col_dir)
    @batches = collate(@raw_files)
  end

  def collate(file_list)
    collated_batches = []
    # 1440000 hits input/output error on MacOS Sonoma, 720000 does not
    # No, I have no idea why. Sometimes it works without issue.
    max_size = 1440000
    current_batch = SampleBatch.new(max_size)
    file_list.each do |file|
      file_path = @path + "/" + file
      if current_batch.append(file_path)
        next
      else
        collated_batches << current_batch
        current_batch = SampleBatch.new(max_size)
        current_batch.append(file_path)
      end
    end

    collated_batches
  end
end

class SampleBatch
  attr_reader :max_size, :actual_size
  attr_accessor :files
  def initialize(max_size)
    @max_size = max_size
    @actual_size = 0
    @files = []
  end

  def append(file_path)
    file_size = File.size(file_path)
    if (@actual_size + file_size) > @max_size
      false
    else
      @files << file_path
      @actual_size += file_size
      true
    end
  end
end

SampleLibrary.new("/home/patrick/Music")

The script makes assumptions about paths and directory structure. The comments in the script detail this. The end-result is a floppies directory, containing a sample-type directory (such as 'drumloops'), containing directories for each collection (such as 'Best Service - Dance Mega Jungle Rave'), containing floppy images with names like 'DLBES001.IMG'.

These GOTEK drives seem fine with directory hierarchies, so it's not an issue if there's two drumloop collection folders that have their names reduced to 'DLBES'.

I did encounter an odd problem on MacOS in the initial draft of the script (as I tend to use Ocenaudio to chop samples). The first draft made use of diskutil and hdiutil to mount the images and change the FAT volume label. Pretty consistently I found that the closer I got to 1.44Mb, the more likely it would be for the copy to hit an input/output error.

Reducing the max size of batches to 720K made this go away (for the most part). But already working with limited space, I didn't feel like doubling the amount of floppy images I'd need to generate. Linux didn't have this issue at all. Although I did need to involve losetup as I couldn't find a way to change a FAT label without presenting the floppy image as a block device.

Surely there's a better way

Probably. I mentioned above there are additional cards that can be added to the SU700. One of these is the Yamaha XS532 SCSI board, which has a 50-pin header internally, and a 25-pin connector externally. This in combination with either a ZuluSCSI or BlueSCSI will give you (I think) up to four virtual hard-disks that are 8GB each.

That doesn't exactly solve the problem though. While it's all well and good we now have an 8GB psuedo-block-device we can store data on, we actually need to be able to mount it on a computer. Unfortunately the SU700 formats the HDD to a strange proprietary filesystem. Worse yet, the way it saves samples is entirely non-standard. Paul Winkler did some work in 2001 to figure out the file formats the SU700 actually exports, and got far enough it's possible to make an AIFF to Yamaha converter. But even then, that doesn't help us put files on the HDD.

One possibility is to abuse Blue/ZuluSCSI's ability to mount bin+cue/iso and the SU700's ability to read AKAI sample CDs. For all it wont let us export samples, 700MB is still nearly 500 times the storage space of a floppy. The formats AKAI uses are well documented, and tools exist to work with them.

Another possibility is to actually try to implement filesystem support in FUSE. Given their sample format is a modification of AIFF, I'd suspect their filesystem is a modification of either FAT or something else. I've ordered a BlueSCSI with the intent to at least dump a copy of a formatted HDD. Who knows, it might not be a lot of work.

Digitakt

Enjoying the SU700, I decided to buy the modern equivalent, which I'd say is the Elektron Digitakt. Of course, the moment I think about buying it, it gets discontinued and they announce (quite literally today) the Digitakt II. Some places still have the original model in stock (Andertons, for one). But if I'm spending a lot, I might as well spend a lot and purchase the current iteration. At least it's more likely to hold value if I eventually sell it. I purchased it from Elevator Sound, who are on my Recommended online stores list.

AirPlay

Also, as an update to the AirPlay stuff - it's still crap. And I can confirm it isn't because of buffer-exhaustion as the latest beta of Finamp makes a point of filling the buffer with minutes of audio before playing.