/* Electric Duet engine plugin for 1tracker */ /* http://krue.net/1tracker/ 2012.05.20 */ /* */ /* Get 1tracker at: */ /* http://shiru.untergrund.net/software.shtml */ void Info(void) { SetTitle("Electric Duet"); SetAbout("Electric Duet player by Paul Lutus. Reclassified Public Domain.\n\nTwo channel Apple II 1-bit music engine. 6502 (No 65c02 needed). Columns are note, timbre."); } uint8[] noteToPeriodTable = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255,240,228,216,204,192,180,172,160,152,144,136, 128,120,114,108,102, 96, 90, 86, 80, 76, 72, 68, 64, 60, 57, 54, 51, 48, 45, 43, 40, 38, 36, 34, 32, 30, 28, 27, 25, 24, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 12, 11, 10, 10, 9, 9, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; void Init(void) { uint channel; uint note,octave; for(channel=0;channel<2;++channel) { AddColumn(channel,COLUMN_NOTE ,0,1); AddColumn(channel,1 ,5,1); AddColumn( -1,COLUMN_EMPTY,0,0); } SetNoteRange("A-2","G#6"); SetPatternHeader(" Row T1 i T2 i"); SetInstrumentsNumber(0); SetLoopSupport(0); AddExportFormat("Prodos Order Disk Image","po"); AddExportFormat("Duet M. File","m"); AddExportFormat("Assembly code","asm"); } void Instrument(void) { //this engine does not support instruments } uint8[] DuetFile; void DuetBinBegin(void) { DuetFile.resize(0); } void DuetBinInst(uint8 inst0, uint8 inst1) { uint here; here = DuetFile.length(); DuetFile.resize(here+3); DuetFile[here+0] = 0x01; DuetFile[here+1] = inst0; DuetFile[here+2] = inst1; } void DuetBinNote(uint8 length, uint8 delay0, uint8 delay1) { uint here; here = DuetFile.length(); DuetFile.resize(here+3); DuetFile[here+0] = length; DuetFile[here+1] = delay0; DuetFile[here+2] = delay1; } void DuetBinEnd(void) { uint here; here = DuetFile.length(); DuetFile.resize(here+1); DuetFile[here+0] = 0x00; } void DuetTextBegin(void) { } void DuetTextInst(uint8 inst0, uint8 inst1) { TXTFilePutString(" .db $01"); TXTFilePutString(",$"+formatInt(inst0,"0h",2)); TXTFilePutString(",$"+formatInt(inst1,"0h",2)); TXTFilePutString("\n"); } void DuetTextNote(uint8 length, uint8 delay0, uint8 delay1) { TXTFilePutString(" .db "); TXTFilePutString("$"+formatInt(length,"0h",2)); TXTFilePutString(",$"+formatInt(delay0,"0h",2)); TXTFilePutString(",$"+formatInt(delay1,"0h",2)); TXTFilePutString("\n"); } void DuetTextEnd(void) { TXTFilePutString(" .db $00\n"); } funcdef void DuetOutBeginFunc(void); DuetOutBeginFunc @DuetOutBegin; funcdef void DuetOutInstFunc(uint8, uint8); DuetOutInstFunc @DuetOutInst; funcdef void DuetOutNoteFunc(uint8, uint8, uint8); DuetOutNoteFunc @DuetOutNote; funcdef void DuetOutEndFunc(void); DuetOutEndFunc @DuetOutEnd; uint8 DuetFreq(uint note) { return (noteToPeriodTable[note]); } void DuetNote(uint length, uint8 note0, uint8 note1) { uint8 length_step; uint8 delay0; uint8 delay1; delay0 = DuetFreq(note0); delay1 = DuetFreq(note1); do { length_step = (length > 0xff) ? 0xff : length; DuetOutNote(length_step, delay0, delay1); length -= length_step; } while (length != 0); } void DoSong(void) { uint length; uint row; uint voice; uint8 speed; uint8 inst0,newinst0; uint8 inst1,newinst1; uint8 note0,newnote0; uint8 note1,newnote1; uint notelength; inst0 = 0xff; inst1 = 0xff; note0 = 0x01; note1 = 0x01; notelength = 0; DuetOutBegin(); speed = GetSongSpeed(); length = GetSongLength(); for (row = 0; row < length; row++) { newinst0 = GetSongData(row,1); newinst1 = GetSongData(row,4); if (((newinst0 != 0) && (newinst0 != inst0)) || ((newinst1 != 0) && (newinst1 != inst1))) { /* flush note buffer */ if (notelength != 0) { DuetNote(notelength,note0,note1); notelength = 0; } /* instrument change */ if (newinst0 != 0) inst0 = newinst0; if (newinst1 != 0) inst1 = newinst1; DuetOutInst(inst0,inst1); } newnote0 = GetSongData(row,0); newnote1 = GetSongData(row,3); if ((newnote0 != 0) || (newnote1 != 0)) { if (notelength != 0) { DuetNote(notelength,note0,note1); notelength = 0; } if (newnote0 != 0) note0 = newnote0; if (newnote1 != 0) note1 = newnote1; } notelength += speed; } /* last note */ if (notelength != 0) { DuetNote(notelength,note0,note1); } DuetOutEnd(); } const uint8[] DiskIILoader = { 0x10,0xa9,0x00,0x85,0x1e,0xa9,0x09,0x85,0x1f,0x20,0x30,0x08,0x4c,0x0c,0x08,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 }; const uint8[] DuetPlayer0830 = { 0xa9,0x01,0x85,0x09,0x85,0x1d,0x48,0x48,0x48,0xd0,0x15,0xc8,0xb1,0x1e,0x85,0x09, 0xc8,0xb1,0x1e,0x85,0x1d,0xa5,0x1e,0x18,0x69,0x03,0x85,0x1e,0x90,0x02,0xe6,0x1f, 0xa0,0x00,0xb1,0x1e,0xc9,0x01,0xf0,0xe3,0xb0,0x0d,0x68,0x68,0x68,0xa2,0x49,0xc8, 0xb1,0x1e,0xd0,0x02,0xa2,0xc9,0x60,0x85,0x08,0x20,0x5d,0x08,0x8e,0xb3,0x08,0x85, 0x06,0xa6,0x09,0x4a,0xca,0xd0,0xfc,0x8d,0xac,0x08,0x20,0x5d,0x08,0x8e,0xeb,0x08, 0x85,0x07,0xa6,0x1d,0x4a,0xca,0xd0,0xfc,0x8d,0xe4,0x08,0x68,0xa8,0x68,0xaa,0x68, 0xd0,0x03,0x2c,0x30,0xc0,0xc9,0x00,0x30,0x03,0xea,0x10,0x03,0x2c,0x30,0xc0,0x85, 0x4e,0x2c,0x00,0xc0,0x30,0xc0,0x88,0xd0,0x02,0xf0,0x06,0xc0,0x00,0xf0,0x04,0xd0, 0x04,0xa4,0x06,0x49,0x40,0x24,0x4e,0x50,0x07,0x70,0x00,0x10,0x09,0xea,0x30,0x09, 0xea,0x30,0x03,0xea,0x10,0x03,0xcd,0x30,0xc0,0xc6,0x4f,0xd0,0x11,0xc6,0x08,0xd0, 0x0d,0x50,0x03,0x2c,0x30,0xc0,0x48,0x8a,0x48,0x98,0x48,0x4c,0x45,0x08,0xca,0xd0, 0x02,0xf0,0x06,0xe0,0x00,0xf0,0x04,0xd0,0x04,0xa6,0x07,0x49,0x80,0x70,0xa3,0xea, 0x50,0xa3,0x00,0x20,0x93,0xfe,0x4c,0xea,0x03,0xff,0xf0,0xe4,0xd8,0xcc,0x00,0x39 }; void CatArray(uint8[] @dest, const uint8[] @src) { uint index, offset, length; offset = dest.length(); length = src.length(); dest.resize(offset+length); for (index = 0; index < length; index++) { dest[offset+index] = src[index]; } } uint8[] DiskImage; void DoDiskImage(void) { DiskImage.resize(0); CatArray(@DiskImage,@DiskIILoader); CatArray(@DiskImage,@DuetPlayer0830); CatArray(@DiskImage,@DuetFile); DiskImage.resize(143360); } void CopySector(uint8[] @dest_image, uint dest_track, uint dest_sector, uint8[] @src_image, uint src_track, uint src_sector) { uint index; uint dest_offset = dest_track*0x1000+dest_sector*0x100; uint src_offset = src_track*0x1000+ src_sector*0x100; for (index= 0; index < 0x100; index++) { dest_image[dest_offset+index] = src_image[src_offset+index]; } } const uint[] Dos33Order = {0x0,0x7,0xe,0x6,0xd,0x5,0xc,0x4,0xb,0x3,0xa,0x2,0x9,0x1,0x8,0xf}; const uint[] ProdosOrder = {0x0,0x8,0x1,0x9,0x2,0xa,0x3,0xb,0x4,0xc,0x5,0xd,0x6,0xe,0x7,0xf}; uint8[] PoImage; void ConvertToPo(void) { uint track,sector; PoImage.resize(143360); for (track = 0; track < 35; track++) { for (sector = 0; sector < 16; sector++) { CopySector(@PoImage,track,ProdosOrder[sector],@DiskImage,track,sector); } } } void Compile(uint format,uint startRow,uint oneShot,uint useMute) { //generate file switch(format) { case 0: { /* play not supported yet*/ break; } case 1: { /* export prodos order disk image */ @DuetOutBegin = @DuetBinBegin; @DuetOutInst = @DuetBinInst; @DuetOutNote = @DuetBinNote; @DuetOutEnd = @DuetBinEnd; DoSong(); DoDiskImage(); ConvertToPo(); DumpOutput(PoImage); break; } case 2: { /* export duet M. file */ @DuetOutBegin = @DuetBinBegin; @DuetOutInst = @DuetBinInst; @DuetOutNote = @DuetBinNote; @DuetOutEnd = @DuetBinEnd; DoSong(); DumpOutput(DuetFile); break; } case 3: { /* export assembly file */ @DuetOutBegin = @DuetTextBegin; @DuetOutInst = @DuetTextInst; @DuetOutNote = @DuetTextNote; @DuetOutEnd = @DuetTextEnd; TXTFileCreate(); DoSong(); TXTFileClose(); break; } } }