/*
* 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;
}
}
}