/*************************************************************************** SN76496.c Copyright (C) 2004 Christoph Reichenbach This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public Licence as published by the Free Software Foundaton; either version 2 of the Licence, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of merchantibility or fitness for a particular purpose. See the GNU General Public Licence for more details. You should have received a copy of the GNU General Public Licence along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. Please contact the maintainer for any program-related bug reports or inquiries. Current Maintainer: Christoph Reichenbach (CR) ***************************************************************************/ /* Tandy/PCJr sequencer for FreeSCI */ #include "sci/sfx/softseq.h" #include "sci/include/sci_midi.h" #define FREQUENCY 44100 #define CHANNELS_NR 3 #define VOLUME_SHIFT 3 static int global_volume = 100; /* Base volume */ static int volumes[CHANNELS_NR] = { 100, 100, 100 }; static int notes[CHANNELS_NR] = {0, 0, 0}; /* Current halftone, or 0 if off */ static int freq_count[CHANNELS_NR] = {0, 0, 0}; static int channel_assigner = 0; static int channels_assigned = 0; static int chan_nrs[CHANNELS_NR] = {-1, -1, -1}; extern sfx_softseq_t sfx_softseq_pcspeaker; /* Forward-declare the sequencer we are defining here */ static int SN76496_set_option(sfx_softseq_t *self, char *name, char *value) { return SFX_ERROR; } static int SN76496_init(sfx_softseq_t *self, byte *patch, int patch_len, byte *patch2, int patch2_len) { return SFX_OK; } static void SN76496_exit(sfx_softseq_t *self) { } static void SN76496_event(sfx_softseq_t *self, byte command, int argc, byte *argv) { int i; int chan = -1; #if 0 fprintf(stderr, "Note [%02x : %02x %02x]\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); #endif if ((command & 0xe0) == 0x80) { int chan_nr = command & 0xf; /* First, test for channel having been assigned already */ if (channels_assigned & (1 << chan_nr)) { /* Already assigned this channel number: */ for (i = 0; i < CHANNELS_NR; i++) if (chan_nrs[i] == chan_nr) { chan = i; break; } } else { /* Assign new channel round-robin */ /* Mark channel as unused: */ if (chan_nrs[channel_assigner] >= 0) channels_assigned &= ~(1 << chan_nrs[channel_assigner]); /* Remember channel: */ chan_nrs[channel_assigner] = chan_nr; /* Mark channel as used */ channels_assigned |= (1 << chan_nrs[channel_assigner]); /* Save channel for use later in this call: */ chan = channel_assigner; /* Round-ropin iterate channel assigner: */ channel_assigner = (channel_assigner + 1) % CHANNELS_NR; } } #if 0 fprintf(stderr, " --> %d [%04x], {%d,%d,%d}@%d\n", chan, channels_assigned, chan_nrs[0],chan_nrs[1],chan_nrs[2],channel_assigner); #endif switch (command & 0xf0) { case 0x80: if (argv[0] == notes[chan]) notes[chan] = 0; break; case 0x90: if (!argv[1]) { if (argv[chan] == notes[chan]) notes[chan] = 0; } else { notes[chan] = argv[0]; volumes[chan] = argv[1]; } break; case 0xb0: if (argv[1] == SCI_MIDI_CHANNEL_NOTES_OFF) notes[chan] = 0; break; default: #if DEBUG fprintf(stderr, "[SFX:PCM-PC] Unused MIDI command %02x %02x %02x\n", command, argc?argv[0] : 0, (argc > 1)? argv[1] : 0); #endif break; /* ignore */ } } #define BASE_NOTE 129 /* A10 */ #define BASE_OCTAVE 10 /* A10, as I said */ static int freq_table[12] = { /* A4 is 440Hz, halftone map is x |-> ** 2^(x/12) */ 28160, /* A10 */ 29834, 31608, 33488, 35479, 37589, 39824, 42192, 44701, 47359, 50175, 53159 }; static inline int get_freq(int note) { int halftone_delta = note - BASE_NOTE; int oct_diff = ((halftone_delta + BASE_OCTAVE * 12) / 12) - BASE_OCTAVE; int halftone_index = (halftone_delta + (12*100)) % 12 ; int freq = (!note)? 0 : freq_table[halftone_index] / (1 << (-oct_diff)); return freq; } void SN76496_poll(sfx_softseq_t *self, byte *dest, int len) { gint16 *buf = (gint16 *) dest; int i; int chan; int freq[CHANNELS_NR]; for (chan = 0; chan < CHANNELS_NR; chan++) freq[chan] = get_freq(notes[chan]); for (i = 0; i < len; i++) { int result = 0; for (chan = 0; chan < CHANNELS_NR; chan++) if (notes[chan]) { int volume = (global_volume * volumes[chan]) >> VOLUME_SHIFT; freq_count[chan] += freq[chan]; while (freq_count[chan] >= (FREQUENCY << 1)) freq_count[chan] -= (FREQUENCY << 1); if (freq_count[chan] - freq[chan] < 0) { /* Unclean rising edge */ int l = volume << 1; result += -volume + (l*freq_count[chan])/freq[chan]; } else if (freq_count[chan] >= FREQUENCY && freq_count[chan] - freq[chan] < FREQUENCY) { /* Unclean falling edge */ int l = volume << 1; result += volume - (l*(freq_count[chan] - FREQUENCY))/freq[chan]; } else { if (freq_count[chan] < FREQUENCY) result += volume; else result += -volume; } } buf[i] = result; } } void SN76496_allstop(sfx_softseq_t *self) { int i; for (i = 0; i < CHANNELS_NR; i++) notes[i] = 0; } void SN76496_volume(sfx_softseq_t *self, int new_volume) { global_volume = new_volume; } sfx_softseq_t sfx_softseq_SN76496 = { "SN76496", "0.1", SN76496_set_option, SN76496_init, SN76496_exit, SN76496_volume, SN76496_event, SN76496_poll, SN76496_allstop, NULL, SFX_SEQ_PATCHFILE_NONE, SFX_SEQ_PATCHFILE_NONE, 0x10, /* Tandy/PCJr channels */ 0, /* No rhythm channel */ 3, /* # of voices */ {FREQUENCY, SFX_PCM_MONO, SFX_PCM_FORMAT_S16_NATIVE} };