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.