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.