ftp.nice.ch/pub/next/audio/player/ModPlayer.2.5.N.s.tar.gz#/ModPlayer/TrackerPlayer.m

This is TrackerPlayer.m in view mode; [Download] [Up]

/*
 *
 *	TrackerPlayer.m
 *	===============
 *
 *	Soundtracker-Module Abspielroutinen von Marc Espie.
 *	Objective-C Implementierung von Ingo Feulner <alf@xenon.stgt.sub.org>
 *
 *	3.4.92: Erste saubere (?????) Implementierung(?)
 *
 *  10.6.92: Source-SÙuberungsaktion von mz
 *
 */

#import <appkit/Panel.h>
#import <sound/sound.h>
#import <sound/sounddriver.h>
#import <math.h>
#import <stdlib.h>
#import <stdio.h>
#import <string.h>
#import <cthreads.h>
#import <mach.h>
#import "TrackerPlayer.h"
#import "Abspieler.h"
#import "TrackerPlayerPrivate.h"

#define	ABSPIELEN 2
#define	BEIM_BEENDEN 1
#define	END_OK 0
#define	LADEN 2
#define	LOAD_AND_PLAY 1
#define	FREQUENCY  (11025)						// 22.6.92 Unsere Sampling-Frequenz
#define	OVERSAMPLE (2)								// 23.6.92 Anzahl der Oversamplings;
#define	FFT_BALKEN 14
#define	FFT_SKIP 1
#define	RING_COUNT 10
#define ACCURACY 16
#define AMIGA_CLOCKFREQ 3575872
#define C fix_to_int(ch->pointer)
#define MAX_LEN 50
#define BUFFERGROESSE (15*1024)
#define SPIEL	100
#define STOP	0
#define MEIN_TAG 10
#define FFT_STOPPED 0
#define FFT_RUNNING 1
#define FFT_STOPIT 2

int					be_stat=END_OK;
mutex_t			be_stat_lock;
int					Aktion_Todo=0;
char				Aktion_Pfad[1024];
extern int	noch_starten;

void initSimpleMsg(simpleMsg *msg);

@implementation TrackerPlayer

int					error, step_table[MAX_PITCH], pitch_table[NUMBER_NOTES];
int					countdown, VSYNC, transpose;
int					ENDE, ring_pos = 0, ring_ausg = 1, spektrum = 1;
int					klang_reset = 0, Musik_Ende = 0, fft_t_stat;
int					buffer_pos, primary, secondary, akt_bufnum;
int					spielstat, threads, sndzaehler;
id					fft;
port_t			fftPort, fftReplyPort;
port_t			devPort, ownerPort, streamPort, replyPort;
mutex_t			spielstat_lock, threads_lock, sndzaehler_lock, fft_t_lock;
condition_t	spiel_akt, ber_akt, master_akt, fft_t_akt;
short				*buffer[RING_COUNT];
short				GlobFFTSpeed = 100;
char				*moduleBuffer;
BOOL				erster_buffer;
float				aktWert[20];
float				nullWert[20] = {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.0, 0.0, 0.0};

void				(*eval[NUMBER_EFFECTS]) ();	// the effect table

struct channel				chan[NUMBER_TRACKS];
struct song_info			*info;
struct sample_info		**voices;


// -----------------------------------------------------------------------
// we sample oversample i for each byte output output frequency

void create_step_table(int oversample)
{
	double	note_fr;												// note frequency (in Hz)
	double	step;
	int			pitch;													// amiga pitch

  step_table[0] = 0;
  for (pitch = 1; pitch < MAX_PITCH; pitch++)
  {
    note_fr = AMIGA_CLOCKFREQ / pitch;
    // int_to_fix(1) is the normalizing factor
    step = note_fr / FREQUENCY * int_to_fix(1) / oversample;
    step_table[pitch] = (int)step;
  }
}


// -----------------------------------------------------------------------
// the musical notes correspond to some specific pitch. It's useful to be
// able to find them back, at least for arpeggii. 

void create_notes_table(void)
{
	double	base, pitch;
	int			i;

	base = AMIGA_CLOCKFREQ / 440;							//SPEED??
  for (i = 0; i < NUMBER_NOTES; i++)
  {
    pitch = base / pow(2.0, i / 12.0);
    pitch_table[i] = pitch;
  }
}


// -----------------------------------------------------------------------

void init_tables(int oversample)
{
	create_step_table(oversample);
	create_notes_table();
}


// -----------------------------------------------------------------------
// The playing mechanism itself. According to the current channel automata,
// we resample the instruments in real time to generate output. 

void resample(struct channel *chan, int oversample, int number)
{
	int								i;										// sample counter
	int								channel;							// channel counter
	int								sampling;							// oversample counter
	SAMPLE						sample;								// sample from the channel
	int								byte[NUMBER_TRACKS];
  struct channel		*ch;									// recomb. of the various data

  // check the existence of samples

  for (channel = 0; channel < NUMBER_TRACKS; channel++)
    if (!chan[channel].samp->start)
    {
      if (!error)
				error = SAMPLE_FAULT;
      chan[channel].mode = DO_NOTHING;
    }

  // do the resampling, i.e., actually play sounds
	// …nderung von if
	
  for (i = 0; i < number && (ENDE != 1); i++)
  {
    for (channel = 0; channel < NUMBER_TRACKS; channel++)
    {
      byte[channel] = 0;
      for (sampling = 0; sampling < oversample; sampling++)
      {
				ch = chan + channel;
				switch (ch->mode)
				{
				case DO_NOTHING:
					break;
				case PLAY:

					// small liability: the sample may have changed, and we may be out
					// of range. However, this routine is time-critical, so we don't
					// check for this very rare case. 

					sample = ch->samp->start[C];
					byte[channel] += sample * ch->volume;
					ch->pointer += ch->step;
					if (C >= ch->samp->length)
					{
						// is there a replay?
						
						if (ch->samp->rp_start)
						{
							ch->mode = REPLAY;
							ch->pointer -= int_to_fix(ch->samp->length);
						}
						else
							ch->mode = DO_NOTHING;
					}
					break;
				case REPLAY:

					// small liability: the sample may have changed, and we may be out
					// of range. However, this routine is time-critical, so we don't
					// check for this very rare case. 

					sample = ch->samp->rp_start[C];
					byte[channel] += sample * ch->volume;
					ch->pointer += ch->step;
					if (C >= ch->samp->rp_length)
						ch->pointer -= int_to_fix(ch->samp->rp_length);
					break;
				}
      }
    }
    output_samples((byte[0] + byte[3]) / oversample,
		   (byte[1] + byte[2]) / oversample, 0);
  }
}


// -----------------------------------------------------------------------
// setting up a given note

void reset_note(struct channel *ch, int note, int pitch)
{
  ch->pointer = 0;
  ch->mode = PLAY;
  ch->pitch = pitch;
  ch->step = step_table[pitch];
  ch->note = note;
  ch->viboffset = 0;
}


// -----------------------------------------------------------------------
// changing the current pitch (value may be temporary, and not stored in
// channel pitch, for instance vibratos. 

void set_current_pitch(struct channel *ch, int pitch)
{
  ch->step = step_table[pitch];
}


// -----------------------------------------------------------------------
// updates the pattern to play in the automaton. Checks that the pattern
// actually exists. 

void set_pattern(struct automaton *a)
{
  int     p;

  if (a->pattern_num >= a->info->length)
  {
    error = UNRECOVERABLE;
    return;
  }

  // there is a level of indirection in the format, i.e., patterns can be
  // repeated. 

  p = a->info->patnumber[a->pattern_num];
  if (p >= a->info->maxpat)
  {
    error = UNRECOVERABLE;
    return;
  }
  a->pattern = a->info->pblocks + p;
}


// -----------------------------------------------------------------------
// initialize all the fields of the automaton necessary to play a given song. 

void init_automaton(struct automaton *a, struct song *song)
{
  a->info = song->info;
  a->pattern_num = 0;										// first pattern
  a->note_num = 0;											// first note in pattern
  a->counter = 0;												// counter for the effect tempo
  a->speed = NORMAL_SPEED;							// this is the default effect tempo
  a->finespeed = NORMAL_FINESPEED;			// this is the fine speed (100%)
  a->do_stuff = DO_NOTHING;

  error = NONE;					// Maybe we should not reset errors at this point.
  set_pattern(a);
}


// -----------------------------------------------------------------------
// Gets to the next pattern, and displays stuff

void advance_pattern(struct automaton *a)
{
  if (++a->pattern_num >= a->info->length)
  {
    a->pattern_num = 0;
    error = ENDED;
  }
  set_pattern(a);
  a->note_num = 0;
}


// -----------------------------------------------------------------------
// process all the stuff which we need to advance in the song, including
// set_speed, set_skip and set_fastskip. 

void next_tick(struct automaton *a)
{
	simpleMsg msg;

  if (a->do_stuff & SET_SPEED && a->new_speed)
  {

    // there are three classes of speed changes: 0 does nothing. <32 is the
    // effect speed (resets the fine speed). >=32 changes the finespeed, so
    // this is 32% to 255% 

    if (a->new_speed > 32)
      a->finespeed = a->new_speed - 32;
    else
    {
      a->speed = a->new_speed;
      a->finespeed = 100;
    }
  }

  if (++a->counter >= a->speed)
  {
    a->counter = 0;
    if (a->do_stuff & SET_FASTSKIP)
    {
      a->pattern_num = a->new_pattern;
      set_pattern(a);
      a->note_num = 0;
			error = ENDED; 				// mz 14.5.92, keine programmierte Wdhlg
    }
    else
    if (a->do_stuff & SET_SKIP)
    {
      advance_pattern(a);
      a->note_num = a->new_note;
			if (error != ENDED)
			{
				initSimpleMsg(&msg);
				msg.Aktion = AKTUELLES_PATTERN;
				msg.Daten = (float *)a->pattern_num;
				msg_rpc(&msg.h, MSG_OPTION_NONE, sizeof(simpleMsg), 0, 0);
			}
    }
    else
    {
      if (++a->note_num >= BLOCK_LENGTH)
			{
				advance_pattern(a);
				if (error != ENDED)
				{
					initSimpleMsg(&msg);
					msg.Aktion = AKTUELLES_PATTERN;
					msg.Daten = (float *)a->pattern_num;
					msg_rpc(&msg.h, MSG_OPTION_NONE, sizeof(simpleMsg), 0, 0);
				}
			}
    }
    a->do_stuff = DO_NOTHING;
  }
}


// -----------------------------------------------------------------------
// sine table for the vibrato effect (could be much more precise)

const int vibrato_table[32] =
			{0, 25, 49, 71, 90, 106, 117, 125, 127, 125, 117, 106, 90,
			 71, 49, 25, 0, -25, -49, -71, -90, -106, -117, -125, -127, -125,
			 -117, -106, -90, -71, -49, -25};


// -----------------------------------------------------------------------
//  setting up effects/doing effects.
//  The set_xxx gets called while parsing the effect,
//  the do_xxx gets called each tick, and update the
//  sound parameters while playing it.

void do_nothing(struct channel *ch)
{
}

// -----------------------------------------------------------------------

void set_nothing(struct automaton *a, struct channel *ch)
{
}


// -----------------------------------------------------------------------
// slide pitch (up or down)

void do_slide(struct channel *ch)
{
  ch->pitch += ch->slide;
  ch->pitch = neuMIN(ch->pitch, MAX_PITCH);
  ch->pitch = neuMAX(ch->pitch, MIN_PITCH);
  set_current_pitch(ch, ch->pitch);
}

// -----------------------------------------------------------------------

void set_upslide(struct automaton *a, struct channel *ch)
{
  ch->adjust = do_slide;
  if (a->para)
    ch->slide = a->para;
}

// -----------------------------------------------------------------------

void set_downslide(struct automaton *a, struct channel *ch)
{
  ch->adjust = do_slide;
  if (a->para)
    ch->slide = -a->para;
}


// -----------------------------------------------------------------------
// modulating the pitch with vibrato

void do_vibrato(struct channel *ch)
{
  int offset;

  // this is a literal transcription of the protracker code. I should rescale
  // the vibrato table at some point 

  ch->viboffset += ch->vibrate;
  ch->viboffset %= 64;
  offset = (vibrato_table[ch->viboffset >> 1] * ch->vibdepth) / 64;

  // temporary update of only the step value, note that we do not change the
  // saved pitch. 

  set_current_pitch(ch, ch->pitch + offset);
}

// -----------------------------------------------------------------------

void set_vibrato(struct automaton *a, struct channel *ch)
{
  ch->adjust = do_vibrato;
  if (a->para)
  {
    ch->vibrate = HI(a->para);
    ch->vibdepth = LOW(a->para);
  }
}


// -----------------------------------------------------------------------
// arpeggio looks a bit like chords: we alternate between two or three notes
// very fast. Issue: we are able to re-generate real chords. Would that be
// better? To try. 

void do_arpeggio(struct channel *ch)
{
  if (++ch->arpindex >= MAX_ARP)
    ch->arpindex = 0;
  set_current_pitch(ch, ch->arp[ch->arpindex]);
}

// -----------------------------------------------------------------------

void set_arpeggio(struct automaton *a, struct channel *ch)
{
  // normal play is arpeggio with 0/0

  if (!a->para)
    return;

  // arpeggio can be installed relative to the previous note, so we have to
  // check that there actually is a current(previous) note 

  if (ch->note == NO_NOTE)
  {
    error = FAULT;
  }
  else
  {
    int note;

    ch->arp[0] = pitch_table[ch->note];
    note = ch->note + HI(a->para);
    if (note < NUMBER_NOTES)
      ch->arp[1] = pitch_table[note];
    else
    {
      error = FAULT;
    }
    note = ch->note + LOW(a->para);
    if (note < NUMBER_NOTES)
      ch->arp[2] = pitch_table[note];
    else
    {
      error = FAULT;
    }
    ch->arpindex = 0;
    ch->adjust = do_arpeggio;
  }
}


// -----------------------------------------------------------------------
// volume slide. Mostly used to simulate waveform control.
// (attack/decay/sustain). 

void do_slidevol(struct channel *ch)
{
  ch->volume += ch->volumerate;
  ch->volume = neuMIN(ch->volume, MAX_VOLUME);
  ch->volume = neuMAX(ch->volume, MIN_VOLUME);
}


// -----------------------------------------------------------------------
// note that volumeslide does not have a ``take default'' behavior. If para
// is 0, this is truly a 0 volumeslide. Issue: is the test really necessary ?
// Can't we do a HI(para) - LOW(para).

void parse_slidevol(struct channel *ch, int para)
{
  if (LOW(para))
    ch->volumerate = -LOW(para);
  else
    ch->volumerate = HI(para);
}

// -----------------------------------------------------------------------

void set_slidevol(struct automaton *a, struct channel *ch)
{
  ch->adjust = do_slidevol;
  parse_slidevol(ch, a->para);
}


// -----------------------------------------------------------------------
// portamento: gets from a given pitch to another. We can simplify the
// routine by cutting it in a pitch up and pitch down part while setting up
// the effect.

void do_portamento(struct channel *ch)
{
  if (ch->pitch < ch->pitchgoal)
  {
    ch->pitch += ch->pitchrate;
    ch->pitch = neuMIN(ch->pitch, ch->pitchgoal);
  }
  else
  if (ch->pitch > ch->pitchgoal)
  {
    ch->pitch -= ch->pitchrate;
    ch->pitch = neuMAX(ch->pitch, ch->pitchgoal);
  }
  set_current_pitch(ch, ch->pitch);
}


// -----------------------------------------------------------------------
// if para and pitch are 0, this is obviously a continuation of the previous
// portamento. 

void set_portamento(struct automaton *a, struct channel *ch)
{
  ch->adjust = do_portamento;
  if (a->para)
    ch->pitchrate = a->para;
  if (a->pitch)
    ch->pitchgoal = a->pitch;
}


// -----------------------------------------------------------------------
// combined commands. 

void do_portaslide(struct channel *ch)
{
  do_portamento(ch);
  do_slidevol(ch);
}

// -----------------------------------------------------------------------

void set_portaslide(struct automaton *a, struct channel *ch)
{
  ch->adjust = do_portaslide;
  parse_slidevol(ch, a->para);
}

// -----------------------------------------------------------------------

void do_vibratoslide(struct channel *ch)
{
  do_vibrato(ch);
  do_slidevol(ch);
}

// -----------------------------------------------------------------------

void set_vibratoslide(struct automaton *a, struct channel *ch)
{
  ch->adjust = do_vibratoslide;
  parse_slidevol(ch, a->para);
}


// -----------------------------------------------------------------------
//  effects that just need a setup part
//
// IMPORTANT: because of the special nature of the player, we can't process
// each effect independently, we have to merge effects from the four channel
// before doing anything about it. For instance, there can be several speed
// changein the same note, only the last one takes effect. 

void set_speed(struct automaton *a, struct channel *ch)
{
  a->new_speed = a->para;
  a->do_stuff |= SET_SPEED;
}

// -----------------------------------------------------------------------

void set_skip(struct automaton *a, struct channel *ch)
{
  // yep, this is BCD.

  a->new_note = HI(a->para) * 10 + LOW(a->para);
  a->do_stuff |= SET_SKIP;
}

// -----------------------------------------------------------------------

void set_fastskip(struct automaton *a, struct channel *ch)
{
  a->new_pattern = a->para;
  a->do_stuff |= SET_FASTSKIP;
}


// -----------------------------------------------------------------------
// immediate effect: starts the sample somewhere off the start. 

void set_offset(struct automaton *a, struct channel *ch)
{
  ch->pointer = int_to_fix(a->para * 256);
}


// -----------------------------------------------------------------------
// change the volume of the current channel. Is effective until there is a
// new set_volume, slide_volume, or an instrument is reloaded explicitly by
// giving its number. Obviously, if you load an instrument and do a
// set_volume in the same note, the set_volume will take precedence. 

void set_volume(struct automaton *a, struct channel *ch)
{
  ch->volume = a->para;
}


// -----------------------------------------------------------------------
// Initialize the whole effect table

void init_effects(void (*table[]) ())
{
  table[0] = set_arpeggio;
  table[15] = set_speed;
  table[13] = set_skip;
  table[11] = set_fastskip;
  table[12] = set_volume;
  table[10] = set_slidevol;
  table[9] = set_offset;
  table[3] = set_portamento;
  table[5] = set_portaslide;
  table[2] = set_upslide;
  table[1] = set_downslide;
  table[4] = set_vibrato;
  table[6] = set_vibratoslide;
  table[14] = set_nothing;
  table[7] = set_nothing;
  table[8] = set_nothing;
}


// -----------------------------------------------------------------------
// init_channel(ch, dummy): setup channel, with initially a dummy sample
// ready to play, and no note. 

void init_channel(struct channel *ch, struct sample_info *dummy)
{
  ch->samp = dummy;
  ch->mode = DO_NOTHING;
  ch->pointer = 0;
  ch->step = 0;
  ch->volume = 0;
  ch->pitch = 0;
  ch->note = NO_NOTE;

  // we don't setup arpeggio values.

	ch->viboffset = 0;
  ch->vibdepth = 0;

  ch->slide = 0;

  ch->pitchgoal = 0;
  ch->pitchrate = 0;

  ch->volumerate = 0;

  ch->vibrate = 0;
  ch->adjust = do_nothing;
}

// -----------------------------------------------------------------------

void setup_effect(struct channel *ch, struct automaton *a, struct event *e)
{
  int     samp, cmd;

  // retrieves all the parameters

  samp = e[a->note_num].sample_number;
  a->note = e[a->note_num].note;
  if (a->note != NO_NOTE)
    a->pitch = pitch_table[a->note];
  else
    a->pitch = e[a->note_num].pitch;
  cmd = e[a->note_num].effect;
  a->para = e[a->note_num].parameters;

  if (a->pitch >= MAX_PITCH)
  {
    a->pitch = 0;
    error = FAULT;
  }

  // load new instrument

  if (samp)
  {
    // note that we can change sample in the middle of a note. This is a
    // *feature*, not a bug (see intromusic6.b) 

    ch->samp = voices[samp];
    ch->volume = voices[samp]->volume;
  }

  // check for a new note: cmd 3 (portamento) is the special case where we do
  // not restart the note. 

  if (a->pitch && cmd != 3)
    reset_note(ch, a->note, a->pitch);
  ch->adjust = do_nothing;

  // do effects

  (eval[cmd]) (a, ch);
}

// -----------------------------------------------------------------------

void play_song(struct song *song, struct pref *pref, int oversample)
{
	int channel;
	struct automaton a;
	simpleMsg msg;
	
  init_automaton(&a, song);
  VSYNC = FREQUENCY * 100 / pref->speed;

  // a repeats of 0 is infinite replays
	
  if (pref->repeats)
    countdown = pref->repeats;
  else
    countdown = 1;

  info = song->info;
  voices = song->samples;

  for (channel = 0; channel < NUMBER_TRACKS; channel++)
    init_channel(chan + channel, voices[0]);

	// …nderung von if

	Musik_Ende = 0;

	initSimpleMsg(&msg);
	msg.Aktion = AKTUELLES_PATTERN;
	msg.Daten = (float *)0;
	msg_rpc(&msg.h, MSG_OPTION_NONE, sizeof(simpleMsg), 0, 0);

  while (countdown && (ENDE != 1))
  {
    for (channel = 0; channel < NUMBER_TRACKS; channel++)
      if (a.counter == 0)
			
				// setup effects

				setup_effect(chan + channel, &a, a.pattern->e[channel]);
      else
			
				//do the effects

				(chan[channel].adjust) (chan + channel);

    // advance player for the next tick

    next_tick(&a);

    // actually output samples
		
    resample(chan, oversample, VSYNC / a.finespeed);

    switch (error)
    {
    case NONE:
      break;
    case ENDED:
      output_samples(0, 0, 1); 					// Ende des Songs (if)
			countdown--;
			if (countdown != 0)
			{
				initSimpleMsg(&msg);
				msg.Aktion = AKTUELLES_PATTERN;
				msg.Daten = (float *)a.pattern_num;
				msg_rpc(&msg.h, MSG_OPTION_NONE, sizeof(simpleMsg), 0, 0);
			}
      break;
    case SAMPLE_FAULT:
      if (!pref->tolerate)
				countdown = 0;
      break;
    case FAULT:
      if (pref->tolerate < 2)
				countdown = 0;
      break;
    case NEXT_SONG:
    case UNRECOVERABLE:
      countdown = 0;
      break;
    default:
      break;
    }
    error = NONE;
  }
	Musik_Ende = 1;
}

// -----------------------------------------------------------------------

int find_note(int pitch)
{
  int     a,
          b,
          i;

  if (pitch == 0)
    return -1;
  a = 0;
  b = NUMBER_NOTES - 1;
  while (b - a > 1)
  {
    i = (a + b) / 2;
    if (pitch_table[i] == pitch)
      return i + transpose;
    if (pitch_table[i] > pitch)
      a = i;
    else
      b = i;
  }
  if (pitch_table[a] - FUZZ <= pitch)
    return a + transpose;
  if (pitch_table[b] + FUZZ >= pitch)
    return b + transpose;
  return NO_NOTE;
}


// -----------------------------------------------------------------------
// c = checkgetc(f): gets a character from file f. Aborts program if file f
// is finished 

int checkgetc(void)
{
  return (int)(*(unsigned char *)moduleBuffer++);
}


// -----------------------------------------------------------------------
// s = getstring(len): gets a soundtracker string from file f. I.e, it is
// a fixed length string terminated by a 0 if too short 

char *getstring(int len)
{
  static char s[MAX_LEN];
  char   *new;
  int     i;

  for (i = 0; i < len; i++)
    s[neuMIN(i, MAX_LEN - 1)] = checkgetc();
  s[neuMIN(len, MAX_LEN - 1)] = '\0';
  new = malloc(strlen(s) + 1);

  return strcpy(new, s);
}


// -----------------------------------------------------------------------
// byteskip(f, len) same as fseek, xcpt it works on stdin 

void byteskip(int len)
{
  int     i;

  for (i = 0; i < len; i++)
    checkgetc();
}


// -----------------------------------------------------------------------
// v = getushort(f) reads an unsigned short from f 

int getushort(void)
{
  int     i;

  i = checkgetc() << 8;
  return i | checkgetc();
}

// -----------------------------------------------------------------------

void fill_sample_info(struct sample_info *info)
{
  info->name = getstring(22);
  info->length = getushort();
  info->finetune = checkgetc();
  info->volume = checkgetc();
  info->volume = neuMIN(info->volume, MAX_VOLUME);
  info->rp_offset = getushort();
  info->rp_length = getushort();

  // the next check is for old modules for which the sample data types are a
  // bit confused, so that what we were expecting to be #words is #bytes. 
  // not sure I understand the -1 myself, though it's necessary to play
  // kawai-k1 correctly 

  if (info->rp_length + info->rp_offset - 1 > info->length)
    info->rp_offset /= 2;

  if (info->rp_length + info->rp_offset > info->length)
    info->rp_length = info->length - info->rp_offset;

  info->length *= 2;
  info->rp_offset *= 2;
  info->rp_length *= 2;

  // in all logic, a 2-sized sample could exist, but this is not the case,
  // and even so, some trackers output empty instruments as being 2-sized. 

  if (info->length <= 2)
    return;

  info->start = (SAMPLE *) calloc(info->length, 1);

  if (info->rp_length > 2)
    info->rp_start = info->start + info->rp_offset;
  else
    info->rp_start = NULL;

  if (info->length > MAX_SAMPLE_LENGTH)
    error = CORRUPT_FILE;
}

// -----------------------------------------------------------------------

void fill_song_info(struct song_info *info)
{
  int i, p;

  info->length = checkgetc();
  checkgetc();
  info->maxpat = -1;
  for (i = 0; i < NUMBER_PATTERNS; i++)
  {
    p = checkgetc();
    if (p >= NUMBER_PATTERNS)
      p = 0;
    if (p > info->maxpat)
      info->maxpat = p;
    info->patnumber[i] = p;
  }
  info->maxpat++;
  if (info->maxpat == 0 || info->length == 0)
    error = CORRUPT_FILE;
}

// -----------------------------------------------------------------------

void fill_event(struct event *e)
{
  int a, b, c, d;

  a = checkgetc();
  b = checkgetc();
  c = checkgetc();
  d = checkgetc();
  e->sample_number = (a & 0x10) | (c >> 4);
  e->effect = c & 0xf;
  e->parameters = d;
  e->pitch = ((a & 15) << 8) | b;
  e->note = find_note(e->pitch);
}

// -----------------------------------------------------------------------

void fill_pattern(struct block *pattern)
{
  int     i, j;

  for (i = 0; i < BLOCK_LENGTH; i++)
    for (j = 0; j < NUMBER_TRACKS; j++)
      fill_event(&(pattern->e[j][i]));
}

// -----------------------------------------------------------------------

void read_sample(struct sample_info *info)
{
  if (info->start != NULL)
  {
		bcopy(moduleBuffer, info->start, info->length);
		moduleBuffer += info->length;
  }
}


// -----------------------------------------------------------------------
//  new_XXX: allocates a new structure for a song.
//  clears each and every field as appropriate.

struct song *new_song(void)
{
  struct song		*new;
  int						i;

  new = (struct song *) malloc(sizeof(struct song));
  new->title = NULL;
  new->info = NULL;
  for (i = 0; i < NUMBER_SAMPLES; i++)
    new->samples[i] = NULL;
  return new;
}

// -----------------------------------------------------------------------

struct sample_info *new_sample_info(void)
{
  struct sample_info *new;

  new = (struct sample_info *) malloc(sizeof(struct sample_info));
  new->name = NULL;
  new->length = 0;
  new->start = NULL;
  new->rp_start = NULL;
  return new;
}

// -----------------------------------------------------------------------

struct song_info *new_song_info(void)
{
  struct song_info *new;

  new = (struct song_info *) malloc(sizeof(struct song_info));
  new->length = 0;
  new->maxpat = -1;
  new->pblocks = NULL;
  return new;
}


// -----------------------------------------------------------------------
// release_song(song): gives back all memory occupied by song. Assume that
// each structure has been correctly allocated by a call to the corresponding
// new_XXX function. 

void release_song(struct song *song)
{
  int i;

  for (i = 0; i < 31; i++)
  {
    if (song->samples[i])
    {
      if (song->samples[i]->start)
				free(song->samples[i]->start);
      if (song->samples[i]->name)
				free(song->samples[i]->name);
      free(song->samples[i]);
    }
  }
  if (song->info)
  {
    if (song->info->pblocks)
      free(song->info->pblocks);
    free(song->info);
  }
  if (song->title)
    free(song->title);
  free(song);
}


// -----------------------------------------------------------------------
// error_song(song): what we should return if there was an error. Actually,
// is mostly useful for its side effects. 

struct song *error_song(struct song *song)
{
  release_song(song);
  return NULL;
}


// -----------------------------------------------------------------------
// bad_sig(f): read the signature on file f and returns !0 if it is not a
// known sig. 

int bad_sig(void)
{
  char    a, b, c, d;

  a = checkgetc();
  b = checkgetc();
  c = checkgetc();
  d = checkgetc();
  if (a == 'M' && b == '.' && c == 'K' && d == '.')
    return 0;
  if (a == 'M' && b == '&' && c == 'K' && d == '!')
    return 0;
  if (a == 'F' && b == 'L' && c == 'T' && d == '4')
    return 0;
  return 1;
}


// -----------------------------------------------------------------------
// s = read_song(f, type): tries to read a song s of type NEW/OLD in file f.
// Might fail, i.e., returns NULL if file is not a mod file of the correct
// type. 

struct song *read_song(int type, int tr)
{
  struct song			*song;
  int							i, ninstr;

  error = NONE;
  transpose = tr;
  if (type == NEW || type == NEW_NO_CHECK)
    ninstr = 31;
  else
    ninstr = 15;

  song = new_song();
  song->title = getstring(20);
  if (error != NONE)
    return error_song(song);

  for (i = 0; i <= 31; i++)
    song->samples[i] = new_sample_info();

  for (i = 1; i <= ninstr; i++)
  {
    fill_sample_info(song->samples[i]);
    if (error != NONE)
      return error_song(song);
  }
  song->info = new_song_info();

  fill_song_info(song->info);

  if (error != NONE)
    return error_song(song);

  if (type == NEW && bad_sig())
    return error_song(song);

  if (type == NEW_NO_CHECK)
    byteskip(4);

  song->info->pblocks = (struct block *)
    malloc(sizeof(struct block) * song->info->maxpat);
  for (i = 0; i < song->info->maxpat; i++)
  {
    fill_pattern(song->info->pblocks + i);
    if (error != NONE)
      return error_song(song);
  }

  for (i = 1; i <= ninstr; i++)
    read_sample(song->samples[i]);

  if (error != NONE)
    return error_song(song);
  return song;
}


// ***********************************************************************
// ***********************************************************************
// ***                                                                 ***
// ***                NeXT-spezifische Abspielroutinen                 ***
// ***                                                                 ***
// ***********************************************************************
// ***********************************************************************
//
//	Copyright (C) 1992 Ingo Feulner, Matthias Zepf und Markus Stoll
//                     <modplayer@xenon.stgt.sub.org>

void set_mix(int percent)
{
	percent *= 256;
	percent /= 100;
	primary = percent;
	secondary = 512 - percent;
}

// -----------------------------------------------------------------------

int open_audio(TrackerPlayer *self)
{
  int protocol;
	int fehler,i;

	protocol = SNDDRIVER_DSP_PROTO_RAW;
	devPort = ownerPort = streamPort = replyPort = 0;
		
	for(i=0;i<RING_COUNT;i++)
	  buffer[i] = malloc(BUFFERGROESSE * sizeof(short));
	akt_bufnum = 0;

  fehler = SNDAcquire(SND_ACCESS_OUT, 5, 0, 0, NULL_NEGOTIATION_FUN,
										 0, &devPort, &ownerPort);
	if(fehler != KERN_SUCCESS)
	{
		NXRunAlertPanel([self->lokaleStringTab valueForStringKey:"Fehler"],
		[self->lokaleStringTab valueForStringKey:"SRbelegt"],
		[self->lokaleStringTab valueForStringKey:"OK"], NULL, NULL);
		[NXApp terminate:self];
	}

	fehler = snddriver_stream_setup(devPort, ownerPort,
		SNDDRIVER_STREAM_TO_SNDOUT_22, vm_page_size/2, 2, 150*1024, 300*1024,
						&protocol, &streamPort);
	if(fehler != KERN_SUCCESS)
	{
		NXRunAlertPanel([self->lokaleStringTab valueForStringKey:"Fehler"],
		[self->lokaleStringTab valueForStringKey:"SRbelegt"],
		[self->lokaleStringTab valueForStringKey:"OK"], NULL, NULL);
		[NXApp terminate:self];
	}		

	fehler = port_allocate(task_self(), &replyPort);
	if(fehler != 0)
	{
		NXRunAlertPanel([self->lokaleStringTab valueForStringKey:"Fehler"],
		[self->lokaleStringTab valueForStringKey:"NoAudio"],
		[self->lokaleStringTab valueForStringKey:"OK"], NULL, NULL);
		[NXApp terminate:self];
	}
	
	snddriver_set_ramp(devPort, 0); // Kein Ramping
		
  buffer_pos = 0;
	sndzaehler = 0;
	
	// Antwortthread erwecken.
	mutex_lock(spielstat_lock);
	spielstat = SPIEL;
	condition_signal(spiel_akt);
	condition_signal(master_akt);
	mutex_unlock(spielstat_lock);
  return (int)SND_RATE_LOW;
}

// -----------------------------------------------------------------------

void close_audio(void)
{
	int i;
	
	ENDE = 1;
	SNDRelease(SND_ACCESS_OUT, devPort, ownerPort);
	port_deallocate(task_self(), replyPort);
	for(i=0;i<RING_COUNT;i++)
	{
		if(buffer[i] != NULL)
			free(buffer[i]);
	}
}

// -----------------------------------------------------------------------

void output_samples(int left, int right, char last_sample)
{
	buffer[akt_bufnum][buffer_pos++] = (right * primary + left * secondary)/256;
	buffer[akt_bufnum][buffer_pos++] = (left * primary + right * secondary)/256;
	buffer[akt_bufnum][buffer_pos++] = (right * primary + left * secondary)/256;
	buffer[akt_bufnum][buffer_pos++] = (left * primary + right * secondary)/256;

  if((buffer_pos >= BUFFERGROESSE) || (last_sample == 1))
  {
		mutex_lock(sndzaehler_lock);
		sndzaehler++;
		mutex_unlock(sndzaehler_lock);
  
		snddriver_stream_start_writing(streamPort, buffer[akt_bufnum], buffer_pos,
			MEIN_TAG, 0, 0, 1, 1, 1, 0, 0, 0, replyPort);

    buffer_pos = 0;
		akt_bufnum = (akt_bufnum + 1) % RING_COUNT;
		mutex_lock (sndzaehler_lock);
		while (sndzaehler > RING_COUNT - 3 && (ENDE != 1))
		{
			condition_wait(ber_akt, sndzaehler_lock);
		}
		mutex_unlock(sndzaehler_lock);
	}
}

// -----------------------------------------------------------------------

void initSimpleMsg(simpleMsg *msg)
{
  msg->h.msg_simple = TRUE; 
  msg->h.msg_size = sizeof(simpleMsg);
  msg->h.msg_local_port = fftReplyPort;
  msg->h.msg_remote_port = fftPort;
  msg->h.msg_type = MSG_TYPE_NORMAL;
  msg->t.msg_type_name = MSG_TYPE_INTEGER_32;
  msg->t.msg_type_size = 32;
  msg->t.msg_type_number = 1;
  msg->t.msg_type_inline = TRUE;
  msg->t.msg_type_longform = FALSE;
  msg->t.msg_type_deallocate = FALSE;
}

// -----------------------------------------------------------------------

void sound_angefangen(void *arg, int tag)
{
	simpleMsg msg;
	
	if(erster_buffer == YES)
	{
		erster_buffer = NO;
		initSimpleMsg(&msg);
		msg.Aktion = PLAYKNOPF_AN;
		msg_rpc(&msg.h, MSG_OPTION_NONE, sizeof(simpleMsg), 0, 0);
	}
}

// -----------------------------------------------------------------------

void sound_beendet(void *arg, int tag)
{
	mutex_lock(sndzaehler_lock);
	sndzaehler--;
	condition_signal(ber_akt);
	mutex_unlock(sndzaehler_lock);

}

// -----------------------------------------------------------------------

void sound_abgebrochen(void *arg, int tag)
{
	ENDE = 1;
	condition_signal(ber_akt);
}

// -----------------------------------------------------------------------

void sound_pausiert(void *arg, int tag)
{
}

// -----------------------------------------------------------------------

void sound_weiter(void *arg, int tag)
{
}

// -----------------------------------------------------------------------

any_t beende_thread(TrackerPlayer *self)
{
	simpleMsg msg;

	mutex_lock(fft_t_lock);
	if(fft_t_stat == FFT_RUNNING)
	{
		fft_t_stat = FFT_STOPIT;
		while(fft_t_stat != FFT_STOPPED)
			condition_wait(fft_t_akt, fft_t_lock);
	}
	mutex_unlock(fft_t_lock);

	mutex_lock(threads_lock);
	if(threads == 2)
	{
		// Auch wirklich schon beim Abspielen?
		mutex_lock(spielstat_lock);
		while(spielstat != SPIEL)
			condition_wait(spiel_akt, spielstat_lock);
		mutex_unlock(spielstat_lock);

		ENDE = 1;
		snddriver_stream_control(streamPort, 0, SNDDRIVER_ABORT_STREAM);

		while(threads != 0)
			condition_wait(master_akt, threads_lock);
	}		
	mutex_unlock(threads_lock);

	mutex_lock(be_stat_lock);
	be_stat = END_OK;
	mutex_unlock(be_stat_lock);
	if (Aktion_Todo)
	{
		initSimpleMsg(&msg);
		msg.Aktion = AKTION_TUN;
		msg_send(&msg.h, MSG_OPTION_NONE, 0);
	}
	cthread_exit(0);
	return (any_t) 0;
}

// -----------------------------------------------------------------------

any_t antwort_thread(id self)
{
	int err;
	snddriver_handlers_t replyHandlers;
	msg_header_t *reply_msg;

	mutex_lock(threads_lock);
	threads++;
	mutex_unlock(threads_lock);

	// Globale Variable fuer sound_angefangen()
	erster_buffer = YES;
	
	cthread_set_name(cthread_self(), "ModPlayer-Antwort");
	
	reply_msg = (msg_header_t *)malloc(MSG_SIZE_MAX);

	replyHandlers.arg       = self;
	replyHandlers.started		= sound_angefangen;
	replyHandlers.completed	= sound_beendet;
	replyHandlers.aborted		= sound_abgebrochen;
	replyHandlers.paused		= sound_pausiert;
	replyHandlers.resumed		= sound_weiter;
		
	
	mutex_lock(spielstat_lock);
	while(spielstat != SPIEL)
		condition_wait(spiel_akt, spielstat_lock);
	mutex_unlock(spielstat_lock);

	while(ENDE != 1)
	{
		reply_msg->msg_size = MSG_SIZE_MAX;
		reply_msg->msg_local_port = replyPort;
		err = msg_receive(reply_msg, MSG_OPTION_NONE, 0);
		if(err == RCV_SUCCESS)
		{
			snddriver_reply_handler(reply_msg, &replyHandlers);
		}
		else
			break;
	}

	free(reply_msg);
	mutex_lock(threads_lock);
	threads--;
	condition_signal(master_akt);
	mutex_unlock(threads_lock);
	cthread_exit(0);
	return (any_t)0;
}
		
// -----------------------------------------------------------------------

any_t fft_thread(TrackerPlayer *self)
{
	simpleMsg msg;
	int count, poff, pnum;
	int i;
	float *fft_erg;

	mutex_lock(fft_t_lock);
	fft_t_stat = FFT_RUNNING;
	mutex_unlock(fft_t_lock);

	while(TRUE)
	{	
		if (spektrum)
		{
			snddriver_stream_nsamples(streamPort, &count);
			count /= 2;
			poff = count % BUFFERGROESSE;
			pnum = (count / BUFFERGROESSE) % RING_COUNT;

			if (poff + 500 > BUFFERGROESSE) 							// mz 3.6.92
			{
				poff = 0;
				pnum = (pnum + 1) % RING_COUNT;
			}

			fft_erg = [fft performFFTWithStereo:&buffer[pnum][poff]];
			for (i=0; i<FFT_BALKEN; i++)
			{
				if (fft_erg[i+1+FFT_SKIP] > 0.85 * aktWert[i])
					aktWert[i] = fft_erg[i+1+FFT_SKIP];
				else
					aktWert[i] = 0.85 * aktWert[i];
			}

			initSimpleMsg(&msg);
			msg.Aktion = FFT_AUSGABE;
			msg.Daten = aktWert;
			msg_rpc(&msg.h, MSG_OPTION_NONE, sizeof(simpleMsg), 0, 0);
			thread_switch (THREAD_NULL, SWITCH_OPTION_WAIT, GlobFFTSpeed);
		}
		else
		{
			if (klang_reset == 1)
			{
				klang_reset = 0;
				initSimpleMsg(&msg);
				msg.Aktion = FFT_CLEAR;
				msg_send(&msg.h, MSG_OPTION_NONE, 0);
			}
			thread_switch (THREAD_NULL, SWITCH_OPTION_WAIT, 500);
		}
		mutex_lock(fft_t_lock);
		if(fft_t_stat == FFT_STOPIT)
		{
			initSimpleMsg(&msg);
			msg.Aktion = FFT_CLEAR;
			msg_send(&msg.h, MSG_OPTION_NONE, 0);

			fft_t_stat = FFT_STOPPED;
			condition_signal(fft_t_akt);
			mutex_unlock(fft_t_lock);
			cthread_exit(0);
		}
		mutex_unlock(fft_t_lock);
	}
	return (any_t) 0;
}

// -----------------------------------------------------------------------

any_t berechnungs_thread(TrackerPlayer *self)
{
  int     oversample;
	int			transpose;

  struct pref pref;
  struct song *song;
	simpleMsg msg;
	
	mutex_lock(threads_lock);
	threads++;
	mutex_unlock(threads_lock);

	cthread_set_name(cthread_self(), "ModPlayer-Berechnung");
		
	// Standardwerte -> Objective-C einbinden
  oversample = OVERSAMPLE;
  transpose  = 0;

	buffer_pos = 0;
	sndzaehler = 0;

	cthread_detach(cthread_fork((cthread_fn_t)fft_thread, (any_t)self));
	mutex_lock(be_stat_lock);
	be_stat = ABSPIELEN;
  mutex_unlock(be_stat_lock);


  pref.type = BOTH;
	if(self->endlosAbspielen != YES)
  	pref.repeats = 1;
	else
		pref.repeats = (unsigned int)-1;

  pref.speed = self->abspielGeschwindigkeit;
  pref.tolerate = 2;

	song = self->abspielSong;

	if(song != NULL)
	{
		play_song(song, &pref, oversample);
		// Stueck ganz durchgespielt?
		if(ENDE != 1)
		{
			mutex_lock(sndzaehler_lock);
			while(sndzaehler > 0 && (ENDE != 1))
			{
				condition_wait(ber_akt, sndzaehler_lock);
			}
			mutex_unlock(sndzaehler_lock);

			// mz 3.6.92
			[self beendeAbspielen];

			initSimpleMsg(&msg);
			msg.Aktion = PLAYKNOPF_AUS;
			msg_rpc(&msg.h, MSG_OPTION_NONE, sizeof(simpleMsg), 0, 0);
		}
	}
	else
	{
		ENDE = 1;
		initSimpleMsg(&msg);
		msg.Aktion = PLAYKNOPF_AUS;
		msg_rpc(&msg.h, MSG_OPTION_NONE, sizeof(simpleMsg), 0, 0);
	}

  close_audio();

	mutex_lock(threads_lock);
	threads--;
	condition_signal(master_akt);
	mutex_unlock(threads_lock);
	cthread_exit(0);
  return (any_t)0;
}

// -----------------------------------------------------------------------

void FFTAusgabeHandler(simpleMsg *msg, TrackerPlayer *self)
{    
	switch(msg->Aktion)
	{
		case FFT_CLEAR:
			[self->fftView neueWerte:nullWert];
			break;

		case FFT_AUSGABE:
			[self->fftView neueWerte:msg->Daten];
			msg_send(&msg->h, MSG_OPTION_NONE, 0);
			break;

		case PLAYKNOPF_AN:
			[[self delegate] perform:@selector(knopfZustand:)
			                 with:(void *)1];
			msg_send(&msg->h, MSG_OPTION_NONE, 0);
			break;
			
		case PLAYKNOPF_AUS:
			[[self delegate] perform:@selector(knopfZustand:)
			                 with:(void *)0];
			[[self delegate] setzeAktuellesPattern: -1];
			msg_send(&msg->h, MSG_OPTION_NONE, 0);
			break;
		
		case AKTUELLES_PATTERN:
			[[self delegate] setzeAktuellesPattern:(int)msg->Daten];
			msg_send(&msg->h, MSG_OPTION_NONE, 0);
			break;
			
		case AKTION_TUN:
			switch (Aktion_Todo)
			{
				case LADEN:
					[[self delegate] ladeModule:self];
					break;
					
				case LOAD_AND_PLAY:
				  noch_starten = 1;
					[[self delegate] ladeModule:self datei:Aktion_Pfad];
					break;
			}
			Aktion_Todo = 0;
	}
}


// ***********************************************************************
// ***********************************************************************
// ***                                                                 ***
// ***         Objective-C Methoden der TrackerPlayer-Klasse           ***
// ***                                                                 ***
// ***********************************************************************
// ***********************************************************************

- init
{
	[super init];
	fft = [[FFT alloc] initPoints:(FFT_BALKEN+1+FFT_SKIP)*2];		//TEST

	spielstat_lock = mutex_alloc();
	threads_lock	 = mutex_alloc();
	sndzaehler_lock= mutex_alloc();
	fft_t_lock = mutex_alloc();
	
	spiel_akt  = condition_alloc();
	ber_akt    = condition_alloc();
	master_akt = condition_alloc();
	fft_t_akt = condition_alloc();
	
	port_allocate(task_self(), &fftPort);
	port_allocate(task_self(), &fftReplyPort);
  DPSAddPort((port_t)fftPort,(DPSPortProc)FFTAusgabeHandler,
		sizeof(simpleMsg), (void *)self, 1);
	threads = 0;
	fft_t_stat = FFT_STOPPED;
	ENDE = 0;
	return self;
}

// -----------------------------------------------------------------------

- setDelegate:sender
{
	delegate = sender;
	return self;
}

// -----------------------------------------------------------------------

- setFFTView:view
{
	fftView = view;
	return self;
}

// -----------------------------------------------------------------------

- setzeStringTab:echteStringTab
{
	lokaleStringTab = echteStringTab;
	return self;
}

// -----------------------------------------------------------------------

- delegate
{
	return delegate;
}

// -----------------------------------------------------------------------

- (BOOL)ladeModuleEin:(char *)module mix:(int)mixwert speed:(int)speed
                               endlos:(BOOL)flag fft:(int)fftspeed
{ 
	int     oversample;
	int			transpose;
	int			schl;								// mz 13.5.92

  struct song *song;
	struct song_info *myinfo;		// if 13.5.92

  oversample = OVERSAMPLE;
  transpose  = 0;

	moduleBuffer = module;
	kanalMix = mixwert;
	GlobFFTSpeed = fftspeed;
	abspielGeschwindigkeit = speed;
	endlosAbspielen = flag;

	set_mix(kanalMix);
	
  init_tables(oversample);
  init_effects(eval);

	if(abspielSong != NULL)
	{
		// Letzten Song freigeben.
		release_song(abspielSong);
		abspielSong = NULL;
	}
	
	song = read_song(NEW, transpose);
	if (!song)
	{
		moduleBuffer = module;
		song = read_song(OLD, transpose);
	}
	
	if(song != NULL)
	{
		myinfo = song->info;
		
		[[self delegate] setzeModuleNamen:song->title laenge:myinfo->length];
		
		for (schl = 1; schl <= 31; schl++)
		{
			if (song->samples[schl]->start)
			{
				if (song->samples[schl]->rp_length > 2)
				{
					[[self delegate] setzeSampleNamen:
					 	song->samples[schl]->name
						nr: schl
						lae: song->samples[schl]->length
						offset: song->samples[schl]->rp_offset
						ofle: song->samples[schl]->rp_length];
				}
				else
				{
					[[self delegate] setzeSampleNamen:
					 	song->samples[schl]->name
						nr: schl
						lae: song->samples[schl]->length
						offset: -1
						ofle: -1];
				}
			}
			else
			{
				[[self delegate] setzeSampleNamen: song->samples[schl]->name //Test
						nr: schl
						lae: -1
						offset: -1
						ofle: -1];
			}
		}
				
		abspielSong = song;
	}
	else
	{
		// Fehlermeldung bringen
		NXRunAlertPanel([lokaleStringTab valueForStringKey:"Fehler"],
		[lokaleStringTab valueForStringKey:"KeinSTMod"],
		[lokaleStringTab valueForStringKey:"OK"], NULL, NULL);

		[NXApp terminate:self];
	}

	return (abspielSong != NULL) ? YES : NO;
}

// -----------------------------------------------------------------------

- (BOOL)beimAbspielen
{
	BOOL flag;
	
	mutex_lock(threads_lock);
	flag = (threads == 2) ? YES : NO;
	mutex_unlock(threads_lock);
	
	return flag;
}

// -----------------------------------------------------------------------

- starteAbspielen
{
	if(abspielSong != NULL)
	{
		mutex_lock(threads_lock);
		if(threads == 0)
		{
			ENDE = 0;
			ring_pos = 0;
			ring_ausg = 1;
			PauseModus = NO;
			
			// Zu diesem Zeitpunkt laufen die Threads noch nicht
			spielstat = STOP;
			
			task_priority(task_self(), 18, TRUE);			
			
			(void)open_audio(self);

			[[delegate willPosAnzeiger] starteZeit];

			// Die beiden Threads starten:
			cthread_detach(cthread_fork((cthread_fn_t)berechnungs_thread,
																												(any_t)self));
		
			cthread_detach(cthread_fork((cthread_fn_t)antwort_thread, (any_t)self));
		}
		mutex_unlock(threads_lock);
	}
	else
	{
		// Fehlermeldung
	}

	return self;
}

// -----------------------------------------------------------------------

- pausiereAbspielen
{	
	if(PauseModus == NO)
	{
		PauseModus = YES;
		snddriver_stream_control(streamPort, 0, SNDDRIVER_PAUSE_STREAM);
	}
	else
	{
		PauseModus = NO;
		snddriver_stream_control(streamPort, 0, SNDDRIVER_RESUME_STREAM);
	}
	
	return self;
}

// -----------------------------------------------------------------------

- beendeAbspielen
{
	PauseModus = NO;
	mutex_lock(be_stat_lock);
	if(be_stat == BEIM_BEENDEN)
	{
		mutex_unlock(be_stat_lock);
		return self;
	}
	be_stat = BEIM_BEENDEN;
	mutex_unlock(be_stat_lock);

	cthread_detach(cthread_fork((cthread_fn_t)beende_thread, (any_t)self));
	return self;
}

// -----------------------------------------------------------------------

- setzeGeschwindigkeit:(int)wert
{
  VSYNC = FREQUENCY * 100 / wert;
	abspielGeschwindigkeit = wert;
	return self;
}

// -----------------------------------------------------------------------

- setzeFFTSpeed:(int)wert
{
  GlobFFTSpeed = wert;
	return self;
}


// -----------------------------------------------------------------------

- setzeStereo:(int)wert
{
	set_mix(wert);
	return self;
}

// -----------------------------------------------------------------------

- setzeEndlos:(BOOL)flag
{
	endlosAbspielen = flag;
	if (flag == NO)
		countdown = 1;
	else
		countdown = (unsigned int)-1;
	return self;
}

// -----------------------------------------------------------------------

- setzeTiefpass:(BOOL)flag
{	
	SNDSetFilter((int)flag);
	return self;
}

@end

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.