Sorted by Squirrels.

Sunday, 6 April 2008

More ACEness



Here's a more portable command-line driven utility to generate wav files ready for ACE's ears. There's a tiny enhancement that ramps up the volume during the lead-in which should prevent the kind of pants-wetting shock that I keep delivering to myself :)



/*
* Support code for ACE - the arduino cassette engine
*
* Example code to write a wav file which can be played to
* a microcontroller running the ACE software.
*
* Should be fairly portable unlike the original example.
*
* Enjoy.
*
*/

#include <iostream>
#include <fstream>

typedef unsigned char uint8_t;

// adjust these to control endiannesses.
//
static const int ENDIAN0 = 0;
static const int ENDIAN1 = 1;
static const int ENDIAN2 = 2;
static const int ENDIAN3 = 3;

static const int ENDIAN0_S = 0;
static const int ENDIAN1_S = 1;

// desired wave structure - AHEM THESE ARE READ-ONLY
// - THEY CAN'T BE CHANGED WITHOUT FUTZING THE CODE. SORRY.
//
static const int CHANNELS = 2;
static const int SAMPLERATE = 44100;
static const int BITSPERSAMPLE = 16;

// this is as loud as we can get. not quite 11,
// but approaching it.
//
static const double maxLevel = (double)SHRT_MAX * 1.0;
static const short maxLevelS = (short)maxLevel;
static const double minLevel = (double)SHRT_MIN * 1.0;
static const short minLevelS = (short)minLevel;


// number of samples per cycle. 60 samples = 1 low or 2 high cycles.
//
static const int f1 = 60;
static const int f1cyc = 1;
static const int f2 = f1 / 2;
static const int f2cyc = f1cyc * 2;

// useful values, maybe
//
static const int usPerCycle = (int)(((double)f1 / 44100) * 1000000.0);
static const int usPerHalfCycle = (int)(((double)f1 / 44100) * 500000.0);
static const int usPerQuarterCycle = (int)(((double)f1 / 44100) * 250000.0);
static const int usPerEighthCycle = (int)(((double)f1 / 44100) * 125000.0);

static const int baudrate = (1000000 / usPerCycle);

// 8 + stop + start;
//
static const int bitsPerFrame = 10;

static const int samplesPerBit = f1 * f1cyc;
static const int samplesPerFrame = bitsPerFrame * samplesPerBit;


static const int LOW_BIT = 0;
static const int HIGH_BIT = !LOW_BIT;

static const int LEADIN_BIT = LOW_BIT;
static const int START_BIT = !LEADIN_BIT;
static const int STOP_BIT = !START_BIT;


// NASTY HACK FOR WRITING WAVS. PLEASE LOOK AWAY NOW.

void CreateWaveFile(std::ostream& output)
{
char chars[4];

output << "RIFF";
*(int*)chars = 16;
output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

output << "WAVE";
output << "fmt ";

*(int*)chars = 16;
output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

*(short*)chars = 1;
output << chars[ENDIAN0_S] << chars[ENDIAN1_S];

*(short*)chars = CHANNELS;
output << chars[ENDIAN0_S] << chars[ENDIAN1_S];

*(int*)chars = SAMPLERATE;
output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

*(int*)chars = (int)(SAMPLERATE*(BITSPERSAMPLE/8*CHANNELS));
output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

*(short*)chars = (short)(BITSPERSAMPLE/8*CHANNELS);
output << chars[ENDIAN0_S] << chars[ENDIAN1_S];

*(short*)chars = (short)BITSPERSAMPLE;
output << chars[ENDIAN0_S] << chars[ENDIAN1_S];

output << "data";
*(int*)chars = 0;
output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];
}


void FinaliseWaveFile(std::ostream& output, size_t bytesWrit)
{
char chars[4];

output.seekp(40);

*(int*)chars = (int)bytesWrit;
output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];

output.seekp(4);

*(int*)chars = (int)(bytesWrit + 36);
output << chars[ENDIAN0] << chars[ENDIAN1] << chars[ENDIAN2] << chars[ENDIAN3];
}

// OK IT'S SAFE TO LOOK AGAIN



// here we represent:
//
// 0 bit as 1 cycle of low frequency,
// 1 bit as 2 cycles of high frequency
//
// where the high frequency is double that of the low.
//
void OutputBit(std::ostream& output, int bit, size_t& written,
short maxval = maxLevelS, short minval = minLevelS)
{
// default to low
//
int cycles = f1cyc;
int samplesPerCycle = f1;

if (bit != 0)
{
cycles = f2cyc;
samplesPerCycle = f2;
}

// generate square pulse, start with rising edge
//
for (int i = 0; i < cycles; ++i)
{
for (int j = 0; j < samplesPerCycle / 2; ++j)
{
// high [1] period
//
output << uint8_t(maxval & 0xff);
output << uint8_t(maxval >> 8);
output << uint8_t(maxval & 0xff);
output << uint8_t(maxval >> 8);
written += 4;
}
for (int j = 0; j < samplesPerCycle / 2; ++j)
{
// low [0] period
//
output << uint8_t(minval & 0xff);
output << uint8_t(minval >> 8);
output << uint8_t(minval & 0xff);
output << uint8_t(minval >> 8);
written += 4;
}
}
}


// outputs a start bit, 8 data bits and a stop bit
//
void OutputByte(std::ostream& output, uint8_t value, size_t& written)
{
OutputBit(output, START_BIT, written);

for (int i = 0; i < 8; ++i)
{
OutputBit(output, (value & 0x80) ? HIGH_BIT : LOW_BIT, written);
value <<= 1;
}

OutputBit(output, STOP_BIT, written);
}


// outputs a lead train of bits, terminated with a start bit.
//
void OutputLeader(std::ostream& output, int milliseconds, size_t& written)
{
int microseconds = (milliseconds * 1000) - usPerCycle;;

// this is a bit of a decoration -
// ramp up the volume during the 1st half of the leader. this might
// prevent some unexpected eardrum-rupture events.. and yes, i know
// that volume should be ramped non-linearly :) this will do for
// the time being.
//
double vol = 0, ramp = 1.0 / (microseconds/(usPerCycle*2));

while (microseconds >= 0)
{
OutputBit(output, LEADIN_BIT, written, (short)(vol * maxLevel), (short)(vol * minLevel));
vol += ramp;
vol = __min(vol, 1.0);

microseconds -= usPerCycle;
}

OutputBit(output, START_BIT, written);
}


void main (int argc, char **argv)
{
if (argc < 3)
{
std::cout << "Usage: makewav [input file] [output file]" << std::endl;
exit(1);
}

std::ifstream inner(argv[1], std::ios_base::binary);
if (!inner.is_open())
{
std::cout << "Could not open input file." << std::endl;
exit(1);
}

std::ofstream outer(argv[2], std::ios_base::binary);
if (!outer.is_open())
{
std::cout << "Could not open output file." << std::endl;
exit(1);
}

// write wav header
//
CreateWaveFile(outer);

// determine input file size
//
inner.seekg(0, std::ios_base::end);
size_t size = (size_t)inner.tellg();
inner.seekg(0);

size_t pc = 0;
size_t written = 0;

// file is structured:
// some seconds of leader
// size msb
// size lsb
// {
// data[512]
// some milliseconds of leader
// }

OutputLeader(outer, 2000, written);

OutputByte(outer, uint8_t(size >> 8), written);
OutputByte(outer, uint8_t(size & 0xff), written);

while (pc != size)
{
// dummy data - replace this with either read from buffer indexed by [pc]
// or a byte get from the file to be encoded
//
OutputByte(outer, inner.get(), written);

++pc;
if (pc % 512 == 0 && pc != size)
{
OutputLeader(outer, 50, written);
}
}

FinaliseWaveFile(outer, written);

outer.close();
}

No comments:

This is how we do it

MMC (9) acorn atom (7) zx81 (7) arduino (5) Atari 800 (3) c128 (3) sd card (3) sd2iec (3) sio2sd (3) tatung einstein (3) 6502 (2) Chuckie egg (2) M5 (2) Max6956 (2) QL (2) RCM (2) Sord (2) assembler (2) avr (2) c64 (2) cadsoft eagle (2) eeprom (2) einSDein (2) mmbeeb (2) multi-cart (2) spi (2) system 80 (2) ufat2 (2) vic20 (2) video genie (2) 6502 second processor (1) 6522 (1) 8255 (1) Acorn BBC Micro (1) Apple 2e (1) Apple ][ 2 two (1) BBC 6502 second processor (1) BBC micro (1) DevicePrint (1) Double Choc Chip Muffins (1) FAT (1) IO (1) Jupiter Ace (1) LED (1) Master 128 (1) PCB (1) PIC (1) POV (1) PROGMEM (1) ST (1) Spectrum 128 (1) antex (1) arcade spinner (1) arduino shield (1) atari (1) atmel (1) bakewell tart (1) beer (1) bird's nest (1) bitbucket (1) brokenated XC special (1) cake (1) cassette (1) cassette interface (1) compact flash (1) convert (1) dac (1) de-yellowing (1) eaca (1) efficient (1) einsdein. z80 (1) eye strain (1) failosophy (1) filesystem (1) finally (1) fram (1) french polishing (1) fuse (1) fuses (1) gaming (1) github (1) glue (1) google chrome (1) heroic failure (1) high voltage programming (1) hot irons (1) if (1) jiffydos (1) joey beltram (1) lego robot (1) library (1) lying (1) machine code (1) matron (1) microcode (1) mmc interface (1) mmc2iec (1) mmm (1) mouse guts (1) multicart (1) oscilloscopes (1) pcm (1) pic32mx (1) porn (1) proto shield (1) purple (1) repo (1) retro computer museum (1) retro hard-on (1) rom box (1) sd (1) sd2mmc (1) seadragon (1) silliness (1) small (1) software master (1) soldering (1) sord m5 (1) spi software master (1) stray capacitance (1) string (1) techadventure (1) test equipment porn (1) ts1000 (1) turtle cheesecake (1) tweaking (1) vc20 (1) video head (1) video ram replacement (1) weewee (1) wingasm (1) wire library (1) wodges of IO (1) xilinx cpld (1) yellowing (1) zx spectrum (1) zxpander (1)

About Sir Morris

My photo

Loves: Old computers, Old Techno, Old ladies. Cake.
Hates: New computers.

Unless otherwise stated all of the original work presented here is:

Creative Commons License
Licensed under a Creative Commons Attribution-Noncommercial 2.5 Generic License.

The work of others where referenced will be attributed appropriately. If I've failed to do this please let me know.