/* * ASAP.ppjava - Java version of ASAP * * Copyright (C) 2007-2009 Piotr Fusik * * This file is part of ASAP (Another Slight Atari Player), * see http://asap.sourceforge.net * * ASAP is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * * ASAP is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ASAP; if not, write to the Free Software Foundation, Inc., * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #define JAVA package net.sf.asap; import java.io.InputStream; import java.io.IOException; final class PokeyState { int audctl; boolean init; int poly_index; int div_cycles; int mute1; int mute2; int mute3; int mute4; int audf1; int audf2; int audf3; int audf4; int audc1; int audc2; int audc3; int audc4; int tick_cycle1; int tick_cycle2; int tick_cycle3; int tick_cycle4; int period_cycles1; int period_cycles2; int period_cycles3; int period_cycles4; int reload_cycles1; int reload_cycles3; int out1; int out2; int out3; int out4; int delta1; int delta2; int delta3; int delta4; int skctl; final int[] delta_buffer = new int[888]; } final class ASAP_State { int cycle; int cpu_pc; int cpu_a; int cpu_x; int cpu_y; int cpu_s; int cpu_nz; int cpu_c; int cpu_vdi; int scanline_number; int nearest_event_cycle; int next_scanline_cycle; int timer1_cycle; int timer2_cycle; int timer4_cycle; int irqst; int extra_pokey_mask; int consol; final byte[] covox = new byte[4]; final PokeyState base_pokey = new PokeyState(); final PokeyState extra_pokey = new PokeyState(); int sample_offset; int sample_index; int samples; int iir_acc_left; int iir_acc_right; public final ASAP_ModuleInfo module_info = new ASAP_ModuleInfo(); int tmc_per_frame; int tmc_per_frame_counter; int current_song; int current_duration; int blocks_played; int silence_cycles; int silence_cycles_counter; final byte[] poly9_lookup = new byte[511]; final byte[] poly17_lookup = new byte[16385]; final byte[] memory = new byte[65536]; } /** Another Slight Atari Player. It interprets music files created for 8-bit Atari computers, emulates the 6502 processor and the POKEY sound chip and generates audio samples. ASAP performs no I/O operations - music data must be passed in byte arrays. */ public final class ASAP { #include "acpu.c" #include "apokeysnd.c" #include "asap.c" private final ASAP_State ast = new ASAP_State(); /** ASAP version. */ public static final String VERSION = "2.0.0"; /** Maximum length of a supported input file. You can assume that files longer than this are not supported by ASAP. */ public static final int MODULE_MAX = 65000; /** Output sample rate. */ public static final int SAMPLE_RATE = ASAP_SAMPLE_RATE; /** WAV file header length. */ public static final int WAV_HEADER_BYTES = 44; /** Output format: 8-bit unsigned. */ public static final int FORMAT_U8 = ASAP_FORMAT_U8; /** Output format: 16-bit signed little-endian. */ public static final int FORMAT_S16_LE = ASAP_FORMAT_S16_LE; /** Output format: 16-bit signed big-endian. */ public static final int FORMAT_S16_BE = ASAP_FORMAT_S16_BE; /** Creates a new instance of the player. The first method you call on the new object must be load. */ public ASAP() { } /** Checks whether the extension of the passed filename is known to ASAP. @param filename filename to check @return true if filename is supported by ASAP */ public static boolean isOurFile(String filename) { return ASAP_IsOurFile(filename); } /** Returns information about a module. @param filename determines file format @param module contents of the file @param module_len length of the file @return file information */ public static ASAP_ModuleInfo getModuleInfo(String filename, byte[] module, int module_len) { ASAP_ModuleInfo module_info = new ASAP_ModuleInfo(); if (!ASAP_GetModuleInfo(module_info, filename, module, module_len)) throw new IllegalArgumentException(); return module_info; } /** Parses a string in the "mm:ss.xxx" format and returns the number of milliseconds represented by the string. @param s string representation of time @return number of milliseconds represented by the string */ public static int parseDuration(String s) { if (s == null || s.length() == 0) return -1; int i = s.indexOf(':'); int r = 0; if (i >= 0) { r = Integer.parseInt(s.substring(0, i)) * 60000; s = s.substring(i + 1); } i = s.indexOf(' '); if (i >= 0) s = s.substring(0, i); r += (int) (Double.parseDouble(s) * 1000); return r; } private static void appendTwoDigits(StringBuffer sb, int x) { sb.append((char) ('0' + x / 10)); sb.append((char) ('0' + x % 10)); } /** Converts number of milliseconds to the "mm:ss" format. @param duration number of milliseconds @return string representation of time */ public static String durationToString(int duration) { StringBuffer sb = new StringBuffer(); if (duration >= 0 && duration < 100 * 60 * 1000) { int seconds = duration / 1000; appendTwoDigits(sb, seconds / 60); sb.append(':'); appendTwoDigits(sb, seconds % 60); /* duration %= 1000; if (duration != 0) { sb.append('.'); appendTwoDigits(sb, duration / 10); duration %= 10; if (duration != 0) sb.append((char) ('0' + duration)); } */ } return sb.toString(); } /** Loads music data. @param filename determines file format @param module contents of the file @param module_len length of the file */ public void load(String filename, byte[] module, int module_len) { if (!ASAP_Load(ast, filename, module, module_len)) throw new IllegalArgumentException(); } /** Returns information about the loaded module. @return information about the loaded module */ public ASAP_ModuleInfo getModuleInfo() { return ast.module_info; } /** Prepares ASAP to play the specified song of the loaded module. @param song zero-based song index @param duration playback time in milliseconds, -1 means indefinitely */ public void playSong(int song, int duration) { ASAP_PlaySong(ast, song, duration); } /** Mutes the selected POKEY channels. @param mask an 8-bit mask which selects POKEY channels to be muted */ public void mutePokeyChannels(int mask) { ASAP_MutePokeyChannels(ast, mask); } /** Returns current position in milliseconds. */ public int getPosition() { return ASAP_GetPosition(ast); } /** Rewinds the current song. @param position the requested absolute position in milliseconds */ public void seek(int position) { ASAP_Seek(ast, position); } /** Fills WAV_HEADER_BYTES leading bytes of the specified buffer with WAV file header. @param buffer the destination buffer @param format format of samples */ public void getWavHeader(byte[] buffer, int format) { ASAP_GetWavHeader(ast, buffer, format); } /** Fills WAV_HEADER_BYTES leading bytes of the specified buffer with WAV file header for at most blocks samples. @param buffer the destination buffer @param format format of samples @param blocks number of samples */ public void getWavHeaderForPart(byte[] buffer, int format, int blocks) { ASAP_GetWavHeaderForPart(ast, buffer, format, blocks); } /** Fills the specified buffer with generated samples. @param buffer the destination buffer @param format format of samples @return number of bytes written in the buffer (less than buffer.length if reached the end of the song) */ public int generate(byte[] buffer, int format) { return ASAP_Generate(ast, buffer, 0, buffer.length, format); } /** Fills a part of the specified buffer with generated samples. @param buffer the destination buffer @param offset starting offset in the buffer @param length number of bytes beginning from offset @param format format of samples @return number of bytes written in the buffer (less than length if reached the end of the song) */ public int generate(byte[] buffer, int offset, int length, int format) { return ASAP_Generate(ast, buffer, offset, length, format); } /** Returns POKEY channel volume. @param channel POKEY channel number (from 0 to 7) @return volume of the specified channel (from 0 to 15) */ public int getPokeyChannelVolume(int channel) { switch (channel) { case 0: return ast.base_pokey.audc1 & 0xf; case 1: return ast.base_pokey.audc2 & 0xf; case 2: return ast.base_pokey.audc3 & 0xf; case 3: return ast.base_pokey.audc4 & 0xf; case 4: return ast.extra_pokey.audc1 & 0xf; case 5: return ast.extra_pokey.audc2 & 0xf; case 6: return ast.extra_pokey.audc3 & 0xf; case 7: return ast.extra_pokey.audc4 & 0xf; default: return 0; } } }