diff options
Diffstat (limited to 'libkmid')
43 files changed, 10245 insertions, 0 deletions
diff --git a/libkmid/Makefile.am b/libkmid/Makefile.am new file mode 100644 index 000000000..5d64b0fcf --- /dev/null +++ b/libkmid/Makefile.am @@ -0,0 +1,24 @@ + +INCLUDES = -I$(srcdir)/.. $(all_includes) + +# For the future: examine if condensing the tons of *_LDFLAGS variables +# into $(all_libraries) isn't better +AM_LDFLAGS = $(LDFLAGS_AS_NEEDED) $(LDFLAGS_NEW_DTAGS) + +libkmidincludedir = $(includedir)/libkmid +libkmidinclude_HEADERS = midiout.h player.h track.h midimapper.h \ + midfile.h dattypes.h midistat.h deviceman.h synthout.h \ + fmout.h gusout.h alsaout.h voiceman.h notearray.h mt32togm.h \ + midispec.h libkmid.h + +lib_LTLIBRARIES = libkmid.la +libkmid_la_SOURCES = midiout.cc player.cc track.cc midimapper.cc \ + midfile.cc dattypes.cc midistat.cc deviceman.cc synthout.cc \ + fmout.cc gusout.cc alsaout.cc voiceman.cc mt32togm.cc notearray.cc \ + libkmid.cc + +libkmid_la_LDFLAGS = $(KDE_MT_LDFLAGS) -version-info 0:95 -no-undefined +libkmid_la_LIBADD = $(LIBASOUND) ../kdecore/libkdecore.la + +DOXYGEN_REFERENCES = kdecore +include ../admin/Doxyfile.am diff --git a/libkmid/alsaout.cc b/libkmid/alsaout.cc new file mode 100644 index 000000000..73effc2e3 --- /dev/null +++ b/libkmid/alsaout.cc @@ -0,0 +1,571 @@ +/************************************************************************** + + alsaout.cc - class AlsaOut which represents an alsa client/port pair + This file is part of LibKMid 0.9.5 + Copyright (C) 2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "alsaout.h" +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include "sndcard.h" +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/param.h> +#include "midispec.h" + + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_ALSA_ASOUNDLIB_H +# include <alsa/asoundlib.h> +#elif defined(HAVE_SYS_ASOUNDLIB_H) +# include <sys/asoundlib.h> +#endif + +#ifdef HAVE_LIBASOUND2 +# define HAVE_ALSA_SEQ 1 +# define snd_seq_flush_output(x) snd_seq_drain_output(x) +#elif defined(HAVE_LIBASOUND) +# define HAVE_ALSA_SEQ 1 +# include <linux/asequencer.h> +#endif + + +SEQ_USE_EXTBUF(); + +class AlsaOut::AlsaOutPrivate +{ +public: +#ifdef HAVE_ALSA_SEQ + AlsaOutPrivate(int _client, int _port, const char *cname,const char *pname) + { + handle=0L; + src=tgt=0L; + queue=0; + tPCN=1; + tgtclient=_client; + tgtport=_port; + tgtname=new char[strlen(cname)+strlen(pname)+3]; + strcpy(tgtname, cname); + strcat(tgtname, " "); + strcat(tgtname, pname); + ev=new snd_seq_event_t; + timerStarted=false; + } +#else + AlsaOutPrivate(int, int, const char *,const char *) + { + } +#endif + + ~AlsaOutPrivate() + { +#ifdef HAVE_ALSA_SEQ + delete ev; + delete tgtname; +#endif + } + +#ifdef HAVE_ALSA_SEQ + snd_seq_t *handle; + int client; + int queue; + snd_seq_addr_t *src; + snd_seq_addr_t *tgt; + + snd_seq_event_t *ev; + int tPCN; + + int tgtclient; + int tgtport; + char *tgtname; + + bool timerStarted; + +#endif +}; + +AlsaOut::AlsaOut(int d,int _client, int _port, const char *cname,const char *pname) : MidiOut (d) +{ + di = new AlsaOutPrivate( _client, _port, cname, pname); + seqfd = 0; + devicetype=KMID_ALSA; + device= d; + + volumepercentage=100; +#ifdef HAVE_ALSA_SEQ +// printf("%d %d %d (%s)\n",device, di->tgtclient, di->tgtport, di->tgtname); +#endif + + _ok=1; +} + +AlsaOut::~AlsaOut() +{ + closeDev(); + delete di; +} + +void AlsaOut::openDev (int) +{ +#ifndef HAVE_ALSA_SEQ + return; +#else + _ok=1; +#ifdef HAVE_LIBASOUND2 + if (snd_seq_open(&di->handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) + fprintf(stderr, "Couldn't open sequencer: %s", snd_strerror(errno)); +#else + if (snd_seq_open(&di->handle, SND_SEQ_OPEN) < 0) + fprintf(stderr, "Couldn't open sequencer: %s", snd_strerror(errno)); +#endif + + di->queue = snd_seq_alloc_queue(di->handle); + if (di->queue < 0) {fprintf(stderr, "Couldn't allocate queue"); return; }; + di->client = snd_seq_client_id(di->handle); + if (di->client < 0) {fprintf(stderr, "Couldn't get client id"); return; }; + di->tgt = new snd_seq_addr_t; + di->tgt->client=di->tgtclient; + di->tgt->port=di->tgtport; + + di->src = new snd_seq_addr_t; + di->src->client = di->client; + int port = snd_seq_create_simple_port(di->handle, NULL, + SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_WRITE + | SND_SEQ_PORT_CAP_READ, SND_SEQ_PORT_TYPE_MIDI_GENERIC); + if ( port < 0 ) + { + delete di->src; + delete di->tgt; + di->src=0; + di->tgt=0; + _ok=0; + time=0; + snd_seq_free_queue(di->handle, di->queue); + snd_seq_close(di->handle); + fprintf(stderr, "Cannot connect to %d:%d\n",di->tgtclient,di->tgtport); + return; + } + di->src->port = port; + + + int r=snd_seq_connect_to(di->handle, di->src->port, di->tgt->client, di->tgt->port); + if (r < 0) { _ok=0; fprintf(stderr, "Cannot connect to %d:%d\n",di->tgtclient,di->tgtport); } + time=0; +#endif +} + +void AlsaOut::closeDev (void) +{ + if (!ok()) return; +#ifdef HAVE_ALSA_SEQ + if (di->handle) + { + if (di->src) + { + snd_seq_delete_simple_port(di->handle,di->src->port); + delete di->src; + di->src=0; + } + if (di->tgt) + { + delete di->tgt; + di->tgt=0; + } + if (di->queue) + { + snd_seq_free_queue(di->handle, di->queue); + snd_seq_close(di->handle); + } + di->handle=0; + } + +#endif +} + +void AlsaOut::initDev (void) +{ +#ifdef HAVE_ALSA_SEQ + int chn; + if (!ok()) return; + uchar gm_reset[5]={0x7e, 0x7f, 0x09, 0x01, 0xf7}; + sysex(gm_reset, sizeof(gm_reset)); + for (chn=0;chn<16;chn++) + { + chnmute[chn]=0; + if (chn!=9) chnPatchChange(chn,0); + chnPressure(chn,64); + chnPitchBender(chn, 0x00, 0x40); + chnController(chn, CTL_MAIN_VOLUME,110*volumepercentage); + chnController(chn, CTL_EXT_EFF_DEPTH, 0); + chnController(chn, CTL_CHORUS_DEPTH, 0); + chnController(chn, 0x4a, 127); + } +#endif +} + +#ifdef HAVE_ALSA_SEQ +void AlsaOut::eventInit(snd_seq_event_t *ev) +{ + snd_seq_ev_clear(ev); + snd_seq_real_time_t tmp; + tmp.tv_sec=(time)/1000; + tmp.tv_nsec=(time%1000)*1000000; +// printf("time : %d %d %d\n",(int)time,(int)tmp.tv_sec, (int)tmp.tv_nsec); + if (!di->src) { fprintf(stderr,"AlsaOut::eventInit : no source\n"); return; } + ev->source = *di->src; + if (!di->tgt) { fprintf(stderr,"AlsaOut::eventInit : no target\n"); return; } + ev->dest = *di->tgt; + + snd_seq_ev_schedule_real(ev, di->queue, 0, &tmp); +} + +void AlsaOut::eventSend(snd_seq_event_t *ev) +{ + /*int err = */ snd_seq_event_output(di->handle, ev); +/* if (err < 0) + return; +*/ +//#ifndef SND_SEQ_IOCTL_GET_CLIENT_POOL + /* + * If this is not defined then block mode writes will not be + * working correctly. Therefore loop until all events are flushed + * out. + */ +/* err = 0; + do { + err = snd_seq_flush_output(di->handle); + if (err > 0) + usleep(2000); + } while (err > 0); + +#endif + + return ; +*/ +} + +void AlsaOut::timerEventSend(int type) +{ + snd_seq_event_t ev; + + ev.queue = di->queue; + ev.dest.client = SND_SEQ_CLIENT_SYSTEM; + ev.dest.port = SND_SEQ_PORT_SYSTEM_TIMER; + + ev.data.queue.queue = di->queue; + + ev.flags = SND_SEQ_TIME_STAMP_REAL | SND_SEQ_TIME_MODE_REL; + ev.time.time.tv_sec = 0; + ev.time.time.tv_nsec = 0; + + ev.type = type; + + snd_seq_event_output(di->handle, &ev); + snd_seq_flush_output(di->handle); +} + +#endif // HAVE_ALSA_SEQ + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::noteOn (uchar , uchar , uchar ) +{ +#else +void AlsaOut::noteOn (uchar chn, uchar note, uchar vel) +{ + if (vel==0) + { + noteOff(chn,note,vel); + } + else + { + eventInit(di->ev); + snd_seq_ev_set_noteon(di->ev,map->channel(chn), map->key(chn,chnpatch[chn],note), vel); + eventSend(di->ev); + } +#endif +#ifdef MIDIOUTDEBUG + printfdebug("Note ON >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::noteOff (uchar , uchar , uchar ) +{ +#else +void AlsaOut::noteOff (uchar chn, uchar note, uchar vel) +{ + eventInit(di->ev); + snd_seq_ev_set_noteoff(di->ev,map->channel(chn), map->key(chn,chnpatch[chn],note), vel); + eventSend(di->ev); +#endif +#ifdef MIDIOUTDEBUG + printfdebug("Note OFF >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::keyPressure (uchar , uchar , uchar ) +{ +#else +void AlsaOut::keyPressure (uchar chn, uchar note, uchar vel) +{ + eventInit(di->ev); + snd_seq_ev_set_keypress(di->ev,map->channel(chn), map->key(chn,chnpatch[chn],note), vel); + eventSend(di->ev); +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::chnPatchChange (uchar , uchar ) +{ +#else +void AlsaOut::chnPatchChange (uchar chn, uchar patch) +{ +#ifdef MIDIOUTDEBUG + printfdebug("PATCHCHANGE [%d->%d] %d -> %d\n", + chn,map->channel(chn),patch,map->patch(chn,patch)); +#endif + eventInit(di->ev); + snd_seq_ev_set_pgmchange(di->ev,map->channel(chn), map->patch(chn,patch)); + eventSend(di->ev); + chnpatch[chn]=patch; +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::chnPressure (uchar , uchar ) +{ +#else +void AlsaOut::chnPressure (uchar chn, uchar vel) +{ + eventInit(di->ev); + snd_seq_ev_set_chanpress(di->ev,map->channel(chn), vel); + eventSend(di->ev); + + chnpressure[chn]=vel; +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::chnPitchBender(uchar ,uchar , uchar ) +{ +#else +void AlsaOut::chnPitchBender(uchar chn,uchar lsb, uchar msb) +{ + map->pitchBender(chn,lsb,msb); + chnbender[chn]=((short)msb<<7) | (lsb & 0x7F); + chnbender[chn]=chnbender[chn]-0x2000; + + eventInit(di->ev); + snd_seq_ev_set_pitchbend(di->ev,map->channel(chn), chnbender[chn]); + eventSend(di->ev); +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::chnController (uchar , uchar , uchar ) +{ +#else +void AlsaOut::chnController (uchar chn, uchar ctl, uchar v) +{ + map->controller(chn,ctl,v); + if ((ctl==11)||(ctl==7)) + { + v=(v*volumepercentage)/100; + if (v>127) v=127; + } + + eventInit(di->ev); + snd_seq_ev_set_controller(di->ev,map->channel(chn), ctl, v); + eventSend(di->ev); + + chncontroller[chn][ctl]=v; +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::sysex(uchar *, ulong ) +{ +#else +void AlsaOut::sysex(uchar *data, ulong size) +{ + eventInit(di->ev); + snd_seq_ev_set_sysex(di->ev, size, data); + eventSend(di->ev); +#endif + +#ifdef MIDIOUTDEBUG + printfdebug("sysex\n"); +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::channelSilence (uchar ) +{ +#else +void AlsaOut::channelSilence (uchar chn) +{ + uchar i; + for ( i=0; i<127; i++) + { + noteOff(chn,i,0); + } +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::channelMute(uchar , int ) +{ +#else +void AlsaOut::channelMute(uchar chn, int a) +{ + if (a==1) + { + chnmute[chn]=a; + channelSilence(chn); + } + else if (a==0) + { + chnmute[chn]=a; + } + /* else ignore the call to this procedure */ +#endif +} + +void AlsaOut::seqbuf_dump (void) +{ + printf("You shouldn't be here.\n"); +} + +void AlsaOut::seqbuf_clean(void) +{ + printf("You shouldn't be here neither.\n"); +} + +void AlsaOut::wait(double ticks) +{ +// SEQ_WAIT_TIME(((int)(ticks/convertrate))); + time=(long int)ticks; + +#ifdef MIDIOUTDEBUG + printfdebug("Wait >\t ticks: %g\n",ticks); +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::tmrSetTempo(int ) +{ +#else +void AlsaOut::tmrSetTempo(int v) +{ + eventInit(di->ev); + di->ev->type = SND_SEQ_EVENT_TEMPO; + snd_seq_ev_set_direct(di->ev); + di->ev->data.queue.queue = di->queue; + di->ev->data.queue.param.value = v; + di->ev->dest.client = SND_SEQ_CLIENT_SYSTEM; + di->ev->dest.port = SND_SEQ_PORT_SYSTEM_TIMER; + snd_seq_event_output_direct(di->handle, di->ev); +#ifdef MIDIOUTDEBUG + printfdebug("SETTEMPO >\t tempo: %d\n",v); +#endif +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::sync(int ) +{ +#else +void AlsaOut::sync(int i) +{ + if (i==1) + { + snd_seq_flush_output(di->handle); + } + + if (di->timerStarted && di->src) + { + eventInit(di->ev); + di->ev->dest = *di->src; + eventSend(di->ev); + snd_seq_flush_output(di->handle); + snd_seq_event_input(di->handle,&di->ev); + } + +#endif +} + +#ifndef HAVE_ALSA_SEQ +void AlsaOut::tmrStart(int ) +{ +#else +void AlsaOut::tmrStart(int tpcn) +{ + int ret; + di->timerStarted=true; + di->tPCN=tpcn; + +#ifdef HAVE_LIBASOUND2 + snd_seq_queue_tempo_t *queuetempo; + snd_seq_queue_tempo_alloca(&queuetempo); + snd_seq_queue_tempo_set_ppq(queuetempo, tpcn); + snd_seq_queue_tempo_set_tempo(queuetempo, 60*1000000/120); + ret = snd_seq_set_queue_tempo(di->handle, di->queue, queuetempo); +#else + snd_seq_queue_tempo_t queuetempo; + memset(&queuetempo, 0, sizeof(queuetempo)); + queuetempo.queue = di->queue; + queuetempo.ppq = tpcn; + queuetempo.tempo = 60*1000000/120; + ret = snd_seq_set_queue_tempo(di->handle, di->queue, &queuetempo); +#endif + + timerEventSend(SND_SEQ_EVENT_START); + snd_seq_start_queue(di->handle,di->queue,NULL); +#endif +} + +void AlsaOut::tmrStop(void) +{ +#ifdef HAVE_ALSA_SEQ + di->timerStarted=false; + timerEventSend(SND_SEQ_EVENT_STOP); +#endif +} + +void AlsaOut::tmrContinue(void) +{ +} + +const char * AlsaOut::deviceName(void) const +{ +#ifdef HAVE_ALSA_SEQ + return di->tgtname; +#else + return 0L; +#endif +} diff --git a/libkmid/alsaout.h b/libkmid/alsaout.h new file mode 100644 index 000000000..fd4a46579 --- /dev/null +++ b/libkmid/alsaout.h @@ -0,0 +1,231 @@ +/* alsaout.cc - class AlsaOut which represents an alsa client/port pair + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _ALSAOUT_H +#define _ALSAOUT_H + +#include <libkmid/midiout.h> + +struct snd_seq_event; +typedef struct snd_seq_event snd_seq_event_t; + +/** + * @short Sends MIDI events to a MIDI devices using ALSA + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class AlsaOut : public MidiOut +{ + friend class DeviceManager; + + protected: + +/** + * @internal + * Total number of devices. + */ + int ndevs; + +/** + * @internal + * Total number of midi ports + */ + int nmidiports; + + double count; + double lastcount; + double lasttime; + double begintime; + int m_rate; + +/** + * @internal + * A "constant" used to convert from milliseconds to the computer rate + */ + double convertrate; + + long int time; + + virtual void seqbuf_dump (void); + virtual void seqbuf_clean(void); + void eventInit(snd_seq_event_t *ev); + void eventSend(snd_seq_event_t *ep); + void timerEventSend(int type); + + public: + + /** + * Constructor. After constructing a MidiOut device, you must open it + * (using openDev() ). Additionally you may want to initialize it + * (with initDev() ), + */ + AlsaOut(int d, int client=64, int port=0, const char *cname="", const char *pname=""); + + /** + * Destructor. It doesn't matter if you close the device ( closeDev() ) + * before you destruct the object because in other case, it will be closed + * here. + */ + virtual ~AlsaOut(); + + /** + * Opens the device. This is generally called from DeviceManager , so you + * shouldn't call this yourself (except if you created the MidiOut object + * yourself. + * @param sqfd a file descriptor of /dev/sequencer + * @see closeDev + * @see initDev + */ + virtual void openDev (int sqfd); + + /** + * Closes the device. It basically tells the device (the file descriptor) + * is going to be closed. + * @see openDev + */ + virtual void closeDev (); + + /** + * Initializes the device sending generic standard midi events and controllers, + * such as changing the patches of each channel to an Acoustic Piano (000), + * setting the volume to a normal value, etc. + */ + virtual void initDev (); + + /** + * @return the device type of the object. This is to identify the + * inherited class that a given object is polymorphed to. + * The returned value is one of these : + * + * @li KMID_EXTERNAL_MIDI if it's a MidiOut object + * @li KMID_SYNTH if it's a SynthOut object (as an AWE device) + * @li KMID_FM if it's a FMOut object + * @li KMID_GUS if it's a GUSOut object + * + * which are defined in midispec.h + * + * @see deviceName + */ + int deviceType () const { return devicetype; } + + /** + * Returns the name and type of this MIDI device. + * @see deviceType + */ + virtual const char * deviceName (void) const; + + /** + * @internal + */ + int rate (void) { return m_rate; } + + /** + * See DeviceManager::noteOn() + */ + virtual void noteOn ( uchar chn, uchar note, uchar vel ); + + /** + * See DeviceManager::noteOff() + */ + virtual void noteOff ( uchar chn, uchar note, uchar vel ); + + /** + * See DeviceManager::keyPressure() + */ + virtual void keyPressure ( uchar chn, uchar note, uchar vel ); + + /** + * See DeviceManager::chnPatchChange() + */ + virtual void chnPatchChange ( uchar chn, uchar patch ); + + /** + * See DeviceManager::chnPressure() + */ + virtual void chnPressure ( uchar chn, uchar vel ); + + /** + * See DeviceManager::chnPitchBender() + */ + virtual void chnPitchBender ( uchar chn, uchar lsb, uchar msb ); + + /** + * See DeviceManager::chnController() + */ + virtual void chnController ( uchar chn, uchar ctl , uchar v ); + + /** + * See DeviceManager::sysex() + */ + virtual void sysex ( uchar *data,ulong size); + + /** + * Mutes all notes being played on a given channel. + */ + virtual void channelSilence ( uchar chn ); + + /** + * Mute or "unmute" a given channel . + * @param chn channel to work on + * @param b if true, the device will ignore subsequent notes played on the chn + * channel, and mute all notes being played on it. If b is false, the channel + * is back to work. + */ + virtual void channelMute ( uchar chn, int b ); + + /** + * Change all channel volume events multiplying it by this percentage correction + * Instead of forcing a channel to a fixed volume, this method allows to + * music to fade out even when it was being played softly. + * @param volper is an integer value, where 0 is quiet, 100 is used to send + * an unmodified value, 200 play music twice louder than it should, etc. + */ + virtual void setVolumePercentage ( int volper ) + { volumepercentage = volper; } + + /** + * Returns true if everything's ok and false if there has been any problem + */ + int ok (void) + { if (seqfd<0) return 0; + return (_ok>0); + } + + virtual void wait (double ticks); + virtual void tmrSetTempo (int v); + virtual void tmrStart (int tpcn); + virtual void tmrStart () { tmrStart(-1); } + virtual void tmrStop (); + virtual void tmrContinue (); + /** + * @internal + * If i==1 syncronizes by cleaning the buffer instead of sending it (in fact, + * this is what synchronizing really means :-) ) + */ + void sync (int i=0); + + class AlsaOutPrivate; + AlsaOutPrivate *di; +}; + +#endif // _ALSAOUT_H diff --git a/libkmid/awe_sup.h b/libkmid/awe_sup.h new file mode 100644 index 000000000..5602cf15f --- /dev/null +++ b/libkmid/awe_sup.h @@ -0,0 +1,50 @@ +/* awe_sup.h - A wrapper on awe_voice.h + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _AWE_SUP_H +#define _AWE_SUP_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_AWE_VOICE_H +#include <awe_voice.h> +#define HAVE_AWE32 +#elif defined(HAVE_LINUX_AWE_VOICE_H) +#include <linux/awe_voice.h> +#define HAVE_AWE32 +#elif defined(HAVE__USR_SRC_SYS_I386_ISA_SOUND_AWE_VOICE_H) +#include "/usr/src/sys/i386/isa/sound/awe_voice.h" +#define HAVE_AWE32 +#elif defined(HAVE__USR_SRC_SYS_GNU_I386_ISA_SOUND_AWE_VOICE_H) +#include "/usr/src/sys/gnu/i386/isa/sound/awe_voice.h" +#define HAVE_AWE32 +#endif + +#ifndef AWE_SET_CHANNEL_MODE +// AWE32 doesn't work if AWE_SET_CHANNEL_MODE isn't defined. +#undef HAVE_AWE32 +#endif + +#endif diff --git a/libkmid/configure.in.in b/libkmid/configure.in.in new file mode 100644 index 000000000..7500e3455 --- /dev/null +++ b/libkmid/configure.in.in @@ -0,0 +1,128 @@ +dnl libkmid's specific checks + +#AC_MSG_CHECKING(if libkmid would compile) +#AC_CACHE_VAL(kde_libkmid_compiles, +#[ +#AC_TRY_COMPILE([ +##ifndef __FreeBSD__ +##include <sys/soundcard.h> +##else +##include <machine/soundcard.h> +##endif +#], +#[ +#], +# kde_libmid_compiles=yes, +#kde_libmid_compiles=no) +#]) +#AC_MSG_RESULT($kde_libmid_compiles) +#if test $kde_libmid_compiles = no; then +# DO_NOT_COMPILE="$DO_NOT_COMPILE libkmid" +#fi + +#AC_MSG_CHECKING([for OSS support]) +#AC_CACHE_VAL(ac_cv_header_soundcard_h, +#[ +#AC_TRY_COMPILE([ +##include <unistd.h> +#], +#[ ], +#ac_cv_header_soundcard_h=yes, +#ac_cv_header_soundcard_h=no) +#]) +#AC_MSG_RESULT($ac_cv_header_soundcard_h) +#if eval "test \"`echo $ac_cv_header_soundcard_h`\" = yes"; then +# AC_DEFINE(HAVE_GETHOSTNAME, 1, [Define if you have getdomainname]) +#fi +#CXXFLAGS="$save_CXXFLAGS" +#]) + + +AC_CHECK_HEADERS(sys/soundcard.h machine/soundcard.h linux/awe_voice.h awe_voice.h /usr/src/sys/i386/isa/sound/awe_voice.h /usr/src/sys/gnu/i386/isa/sound/awe_voice.h) + +dnl check for ALSA audio support + +kde_with_alsa=yes +AC_ARG_WITH(alsa, AC_HELP_STRING([--with-alsa],[enable libKMid ALSA support]), +[kde_with_alsa=$withval]) + +if test "$kde_with_alsa" = "yes"; then +AC_DEFUN([AC_CHECK_LIBASOUND], +[ + ac_ldflags_save="$LDFLAGS" + LDFLAGS="$all_libraries $LDFLAGS" + kde_has_asoundlib=no + + AC_CHECK_HEADERS([ sys/asoundlib.h alsa/asoundlib.h ], + [ + kde_has_asoundlib=yes + ]) + + dnl trial and error version check for ALSA 0.5.x / ALSA 0.9.x + AC_LANG_SAVE + AC_LANG_C + if test "x$kde_has_asoundlib" = "xyes"; then + AC_TRY_COMPILE([ + #include "confdefs.h" + #ifdef HAVE_SYS_ASOUNDLIB_H + #include <sys/asoundlib.h> + #endif + #ifdef HAVE_ALSA_ASOUNDLIB_H + #include <alsa/asoundlib.h> + #endif + ],[ + #if ((SND_LIB_MAJOR == 0) && (SND_LIB_MINOR == 9)) || (SND_LIB_MAJOR == 1) + /* we have ALSA 0.9.x or 1.x */ + #else + #error not ALSA 0.9.x + #endif + ], + kde_has_alsa_0_9=yes, + kde_has_alsa_0_9=no) + fi + + if test "x$kde_has_asoundlib" = "xyes"; then + AC_TRY_COMPILE([ + #include "confdefs.h" + #ifdef HAVE_SYS_ASOUNDLIB_H + #include <sys/asoundlib.h> + #endif + #ifdef HAVE_ALSA_ASOUNDLIB_H + #include <alsa/asoundlib.h> + #endif + ],[ + #if (SND_LIB_MAJOR == 0) && (SND_LIB_MINOR == 5) + /* we have ALSA 0.5.x */ + #else + #error not ALSA 0.5.x + #endif + ], + kde_has_alsa_0_5=yes, + kde_has_alsa_0_5=no) + fi + AC_LANG_RESTORE + + if test "x$kde_has_asoundlib" = "xyes"; then + AC_CHECK_LIB(asound,snd_seq_create_simple_port,[ + if test "x$kde_has_alsa_0_5" = "xyes"; then + LIBASOUND="-lasound" + AC_DEFINE(HAVE_LIBASOUND, 1, + [Define if you have libasound.so.1 (required for ALSA 0.5.x support)]) + fi + if test "x$kde_has_alsa_0_9" = "xyes"; then + LIBASOUND="-lasound" + AC_DEFINE(HAVE_LIBASOUND2, 1, + [Define if you have libasound.so.2 (required for ALSA 0.9.x support)]) + AC_CHECK_LIB(asound,snd_pcm_resume,[ + AC_DEFINE(HAVE_SND_PCM_RESUME, 1, + [Define if libasound has snd_pcm_resume()])]) + fi + ]) + fi + AC_SUBST(LIBASOUND) + LDFLAGS="$ac_ldflags_save" +]) +AC_CHECK_LIBASOUND +fi + +AC_SUBST(LIBASOUND) diff --git a/libkmid/dattypes.cc b/libkmid/dattypes.cc new file mode 100644 index 000000000..821abd00a --- /dev/null +++ b/libkmid/dattypes.cc @@ -0,0 +1,110 @@ +/************************************************************************** + + dattypes.cc - Some always useful definitions and functions + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "dattypes.h" +#include <stdio.h> + +ushort readShort(FILE *fh) +{ + uchar c1; + uchar c2; + + fread(&c1,1,1,fh); + fread(&c2,1,1,fh); + return (c1<<8)|c2; +} + +ulong readLong(FILE *fh) +{ + uchar c1; + uchar c2; + uchar c3; + uchar c4; + ulong l; + + fread(&c1,1,1,fh); + fread(&c2,1,1,fh); + fread(&c3,1,1,fh); + fread(&c4,1,1,fh); + l=((c1<<24)|(c2<<16)|(c3<<8)|c4); + return l; +} + +#ifdef DEBUG + +void printfdebug(const char *format, int a, int b, int c) +{ + char *s=(char *)format; + int i=0; + while (*s!=0) + { + if (*s=='%') i++; + s++; + } + switch (i) + { + case (1) : fprintf(stderr,format,a); break; + case (2) : fprintf(stderr,format,a,b); break; + case (3) : fprintf(stderr,format,a,b,c); break; + default : fprintf(stderr,format); break; + } + +} + +void printfdebug(const char *format, int a, long b) +{ + fprintf(stderr,format,a,b); +} + +void printfdebug(const char *format, double a, double b, double c) +{ + char *s=(char *)format; + int i=0; + while (*s!=0) + { + if (*s=='%') i++; + s++; + } + switch (i) + { + case (1) : fprintf(stderr,format,a); break; + case (2) : fprintf(stderr,format,a,b); break; + case (3) : fprintf(stderr,format,a,b,c); break; + default : fprintf(stderr,format); break; + } + +} +#else + +void printfdebug(const char *, int , int , int ) +{ +} +void printfdebug(const char *, int , long ) +{ +} +void printfdebug(const char *, double , double , double ) +{ +} +#endif diff --git a/libkmid/dattypes.h b/libkmid/dattypes.h new file mode 100644 index 000000000..0e647980f --- /dev/null +++ b/libkmid/dattypes.h @@ -0,0 +1,55 @@ +/* dattypes.h - Some useful definitions and functions + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _DATTYPES_H +#define _DATTYPES_H + +#include <stdio.h> +#include <sys/types.h> + +#undef uchar +#undef ushort +#undef ulong + +/** + * Unsigned char + */ +typedef unsigned char uchar; + +/** + * Unsigned short + */ +typedef unsigned short ushort; + +/** + * Unsigned long + */ +typedef unsigned long ulong; + +ushort readShort(FILE *fh); +ulong readLong (FILE *fh); + +void printfdebug(const char *s,int a=0,int b=0, int c=0); +void printfdebug(const char *s,int a,long b); +void printfdebug(const char *s,double a,double b=0, double c=0); + +#endif diff --git a/libkmid/deviceman.cc b/libkmid/deviceman.cc new file mode 100644 index 000000000..7c8e0f48c --- /dev/null +++ b/libkmid/deviceman.cc @@ -0,0 +1,830 @@ +/************************************************************************** + + deviceman.cc - The device manager, that hides the use of midiOut + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + $Id$ + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "deviceman.h" +#include "midiout.h" +#include <stdio.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <errno.h> +#include "sndcard.h" +#include "synthout.h" +#include "fmout.h" +#include "gusout.h" +#include "alsaout.h" +#include "midimapper.h" +#include "midispec.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#ifdef HAVE_ALSA_ASOUNDLIB_H +# define HAVE_ALSA_SUPPORT +# include <alsa/asoundlib.h> +#elif defined(HAVE_SYS_ASOUNDLIB_H) +# define HAVE_ALSA_SUPPORT +# include <sys/asoundlib.h> +#else +#ifdef HAVE_LIBASOUND2 +# define HAVE_ALSA_SUPPORT +# include <sound/asound.h> +# include <sound/asequencer.h> +#elif defined(HAVE_LIBASOUND) +# define HAVE_ALSA_SUPPORT +# include <linux/asequencer.h> +#endif +#endif + +#if 1 +#include <kinstance.h> +#include <kglobal.h> +#include <kconfig.h> +#endif + +//#define DEVICEMANDEBUG +//#define GENERAL_DEBUG_MESSAGES + +SEQ_DEFINEBUF (4096); + +#define CONTROLTIMER + +#ifdef GENERAL_DEBUG_MESSAGES +void DEBUGPRINTF(const char *format) +{ + printf(format); +} + +void DEBUGPRINTF(const char *format,int i) +{ + printf(format,i); +} + +void DEBUGPRINTF(const char *format,const char *s) +{ + printf(format,s); +} + +#else + +void DEBUGPRINTF(const char *) { } +void DEBUGPRINTF(const char *,int ) { } +void DEBUGPRINTF(const char *,const char * ) { } + +#endif + + +DeviceManager::DeviceManager(int def) +{ +#if 1 + if (def==-1) + { + KInstance *tmp_instance=0L; + if (!KGlobal::_instance) tmp_instance=new KInstance("nonKDEapp"); + KConfig *config = new KConfig("kcmmidirc", true); + + config->setGroup("Configuration"); + default_dev=config->readNumEntry("midiDevice",0); + if ( default_dev < 0 ) + default_dev=0; + QString mapurl(config->readPathEntry("mapFilename")); + if ((config->readBoolEntry("useMidiMapper", false))&&(!mapurl.isEmpty())) + { + mapper_tmp = new MidiMapper( mapurl.mid(mapurl.find(":")+1 ).local8Bit() ); + } + else + mapper_tmp = 0L; + + delete config; + delete tmp_instance; + } + else +#endif + { + default_dev = def; + mapper_tmp = 0L; + } + + initialized=0; + _ok=1; + alsa=false; + device = 0L; + m_rate=0; + convertrate=10; + seqfd=-1; + timerstarted=0; + n_midi=0; + n_synths=0; + n_total=0; + midiinfo=0L; + synthinfo=0L; + for (int i=0;i<16;i++) chn2dev[i]=default_dev; +} + +DeviceManager::~DeviceManager(void) +{ + closeDev(); + if (device) + { + for (int i=0;i<n_total;i++) + delete device[i]; + delete[] device; + device=0L; + } +#ifdef HAVE_OSS_SUPPORT + delete[] midiinfo; + delete[] synthinfo; +#endif +} + +int DeviceManager::ok(void) +{ + int r=_ok; + _ok=1; + return r; +} + +int DeviceManager::checkInit(void) +{ + if (initialized==0) + { + int r=initManager(); + if (default_dev>=n_total) default_dev=0; + DEBUGPRINTF("check : %d\n",r); + return r; + } + return 0; +} + +void DeviceManager::checkAlsa(void) +{ +#ifdef HAVE_SYS_STAT_H + struct stat buf; + stat("/proc/asound", &buf); + if ((stat("/proc/asound", &buf) == 0 ) && (S_ISDIR(buf.st_mode))) + alsa=true; + else + alsa=false; +#else +#warning "ALSA won't be found at runtime" + alsa=false; +#endif +} + +int DeviceManager::initManager(void) +{ + checkAlsa(); + + if (!alsa) // We are using OSS + { +#ifdef HAVE_OSS_SUPPORT + n_synths=0; + n_midi=0; + n_total=0; + + seqfd = open("/dev/sequencer", O_WRONLY | O_NONBLOCK, 0); + if (seqfd==-1) + { + fprintf(stderr,"ERROR: Couldn't open /dev/sequencer to get some information\n"); + _ok=0; + return -1; + } + ioctl(seqfd,SNDCTL_SEQ_NRSYNTHS,&n_synths); + ioctl(seqfd,SNDCTL_SEQ_NRMIDIS,&n_midi); + n_total=n_midi+n_synths; + + + if (n_midi==0) + { + fprintf(stderr,"ERROR: There's no midi port\n"); + /* This could be a problem if the user don't have a synth neither, + but not having any of both things is unusual */ + // _ok=0; + // return 1; + } + + device=new MidiOut*[n_total]; + midiinfo=new midi_info[n_midi]; + synthinfo=new synth_info[n_synths]; + + int i; + for (i=0;i<n_midi;i++) + { + midiinfo[i].device=i; + if (ioctl(seqfd,SNDCTL_MIDI_INFO,&midiinfo[i])!=-1) + { +#ifdef GENERAL_DEBUG_MESSAGES + printf("----\n"); + printf("Device : %d\n",i); + printf("Name : %s\n",midiinfo[i].name); + printf("Device type : %d\n",midiinfo[i].dev_type); +#endif + } + device[i]=new MidiOut(i); + } + + for (i=0;i<n_synths;i++) + { + synthinfo[i].device=i; + if (ioctl(seqfd,SNDCTL_SYNTH_INFO,&synthinfo[i])!=-1) + { +#ifdef GENERAL_DEBUG_MESSAGES + printf("----\n"); + printf("Device : %d\n",i); + printf("Name : %s\n",synthinfo[i].name); + switch (synthinfo[i].synth_type) + { + case (SYNTH_TYPE_FM) : printf("FM\n");break; + case (SYNTH_TYPE_SAMPLE) : printf("Sample\n");break; + case (SYNTH_TYPE_MIDI) : printf("Midi\n");break; + default : printf("default type\n");break; + }; + switch (synthinfo[i].synth_subtype) + { + case (FM_TYPE_ADLIB) : printf("Adlib\n");break; + case (FM_TYPE_OPL3) : printf("Opl3\n");break; + case (MIDI_TYPE_MPU401) : printf("Mpu-401\n");break; + case (SAMPLE_TYPE_GUS) : printf("Gus\n");break; + default : printf("default subtype\n");break; + } +#endif + if (synthinfo[i].synth_type==SYNTH_TYPE_FM) + device[i+n_midi]=new FMOut(i,synthinfo[i].nr_voices); + else if ((synthinfo[i].synth_type==SYNTH_TYPE_SAMPLE)&& + (synthinfo[i].synth_subtype==SAMPLE_TYPE_GUS)) + device[i+n_midi]=new GUSOut(i,synthinfo[i].nr_voices); + else + device[i+n_midi]=new SynthOut(i); + } + } + + close(seqfd); +#else // There's no OSS support and ALSA wasn't detected + // It must be one of those systems coolo is using :-) + + n_synths=0; + n_midi=0; + n_total=0; + device=0L; + midiinfo=0L; + synthinfo=0L; + +#endif + + } + else + { // We are using ALSA + +#ifdef HAVE_ALSA_SUPPORT + int client, port; +#ifdef HAVE_LIBASOUND2 + snd_seq_t *handle=0; + snd_seq_client_info_t *clienti; + snd_seq_client_info_malloc(&clienti); + snd_seq_port_info_t *porti; + snd_seq_port_info_malloc(&porti); + + snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0); + if (!handle) { printf("handle==0\n"); return -1; }; + snd_seq_system_info_t *info; + snd_seq_system_info_malloc(&info); + if (!info) { printf("info==0\n"); return -1; }; + snd_seq_system_info(handle, info); + + n_total=0; + n_midi=0; + n_synths=0; + + device=new MidiOut*[snd_seq_system_info_get_clients(info)*snd_seq_system_info_get_ports(info)]; + unsigned int k=SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_WRITE ; + for (client=0 ; client<snd_seq_system_info_get_clients(info) ; client++) + { + snd_seq_get_any_client_info(handle, client, clienti); + for (port=0 ; port<snd_seq_client_info_get_num_ports(clienti) ; port++) + { + snd_seq_get_any_port_info(handle, client, port, porti); + if (( snd_seq_port_info_get_capability(porti) & k ) == k) + { + device[n_midi]=new AlsaOut(n_midi,client, port, snd_seq_client_info_get_name(clienti), snd_seq_port_info_get_name(porti)); + n_midi++; + }; + } + } + snd_seq_client_info_free(clienti); + snd_seq_port_info_free(porti); + snd_seq_system_info_free(info); +#else + snd_seq_t *handle=0; + snd_seq_client_info_t clienti; + snd_seq_port_info_t porti; + + snd_seq_open(&handle, SND_SEQ_OPEN); + if (!handle) { printf("handle(2)==0\n"); return -1; }; + + snd_seq_system_info_t info; + info.clients=info.ports=0; + snd_seq_system_info(handle, &info); + + n_total=0; + n_midi=0; + n_synths=0; + + device=new MidiOut*[info.clients*info.ports]; + unsigned int k=SND_SEQ_PORT_CAP_SUBS_WRITE | SND_SEQ_PORT_CAP_WRITE ; + for (client=0 ; client<info.clients ; client++) + { + snd_seq_get_any_client_info(handle, client, &clienti); + for (port=0 ; port<clienti.num_ports ; port++) + { + snd_seq_get_any_port_info(handle, client, port, &porti); + if (( porti.capability & k ) == k) + { + device[n_midi]=new AlsaOut(n_midi,client, port, clienti.name, porti.name); + n_midi++; + }; + } + } +#endif + n_total=n_midi; + + snd_seq_close(handle); +#else + + // Note: Please don't add i18n for the text below, thanks :) + + fprintf(stderr,"Sorry, this KMid version was compiled without \n"); + fprintf(stderr,"ALSA support but you're using ALSA . \n"); + fprintf(stderr,"Please compile KMid for yourself or tell the people\n"); + fprintf(stderr,"at your Linux distribution to compile it themselves\n"); +#endif + } + + if (mapper_tmp!=0L) setMidiMap(mapper_tmp); + + initialized=1; + + return 0; +} + +void DeviceManager::openDev(void) +{ + if (checkInit()<0) + { + DEBUGPRINTF("DeviceManager::openDev : Not initialized\n"); + _ok = 0; + return; + } + _ok=1; + + if (!alsa) + { +#ifdef HAVE_OSS_SUPPORT + seqfd = open("/dev/sequencer", O_WRONLY | O_NONBLOCK, 0); + if (seqfd==-1) + { + fprintf(stderr,"Couldn't open the MIDI sequencer device (/dev/sequencer)\n"); + _ok=0; + return; + } + _seqbufptr = 0; + ioctl(seqfd,SNDCTL_SEQ_RESET); + //ioctl(seqfd,SNDCTL_SEQ_PANIC); + m_rate=0; + int r=ioctl(seqfd,SNDCTL_SEQ_CTRLRATE,&m_rate); + if ((r==-1)||(m_rate<=0)) m_rate=HZ; + + convertrate=1000/m_rate; + +#endif + } + else seqfd=0L; // ALSA + +// DEBUGPRINTF("Opening devices : "); + for (int i=0;i<n_total;i++) + { + device[i]->openDev(seqfd); +// DEBUGPRINTF("%s ",device[i]->deviceName()); + } +// DEBUGPRINTF("\n"); + for (int i=0;i<n_total;i++) if (!device[i]->ok()) _ok=0; + if (_ok==0) + { + for (int i=0;i<n_total;i++) device[i]->closeDev(); +// DEBUGPRINTF("DeviceMan :: ERROR : Closing devices\n"); + return; + } + +// DEBUGPRINTF("Devices opened\n"); +} + +void DeviceManager::closeDev(void) +{ + if (alsa) + { + if (device) + for (int i=0;i<n_total;i++) + if (device[i]) device[i]->closeDev(); + + return; + } + +#ifdef HAVE_OSS_SUPPORT + if (seqfd==-1) return; + tmrStop(); + if (device) + for (int i=0;i<n_total;i++) + if (device[i]) device[i]->closeDev(); + /* + DEBUGPRINTF("Closing devices : "); + if (device!=NULL) for (int i=0;i<n_total;i++) + { + device[i]->initDev(); + DEBUGPRINTF("%s ",device[i]->deviceName()); + + // device[i]->closeDev(); + }; + DEBUGPRINTF("\n"); + */ + close(seqfd); + seqfd=-1; +#endif +} + +void DeviceManager::initDev(void) +{ + if (device!=0L) + { +// DEBUGPRINTF("Initializing devices :"); + for (int i=0;i<n_total;i++) + { + device[i]->initDev(); + DEBUGPRINTF("%s ",device[i]->deviceName()); + } + DEBUGPRINTF("\n"); + } +} + +void DeviceManager::noteOn ( uchar chn, uchar note, uchar vel ) +{ + MidiOut *midi=chntodev(chn); + if (midi) midi->noteOn(chn,note,vel); +} +void DeviceManager::noteOff ( uchar chn, uchar note, uchar vel ) +{ + MidiOut *midi=chntodev(chn); + if (midi) midi->noteOff(chn,note,vel); +} +void DeviceManager::keyPressure ( uchar chn, uchar note, uchar vel ) +{ + MidiOut *midi=chntodev(chn); + if (midi) midi->keyPressure(chn,note,vel); +} +void DeviceManager::chnPatchChange ( uchar chn, uchar patch ) +{ + MidiOut *midi=chntodev(chn); + if (midi) midi->chnPatchChange(chn,patch); +} +void DeviceManager::chnPressure ( uchar chn, uchar vel ) +{ + MidiOut *midi=chntodev(chn); + if (midi) midi->chnPressure(chn,vel); +} +void DeviceManager::chnPitchBender ( uchar chn, uchar lsb, uchar msb ) +{ + MidiOut *midi=chntodev(chn); + if (midi) midi->chnPitchBender(chn,lsb,msb); +} +void DeviceManager::chnController ( uchar chn, uchar ctl , uchar v ) +{ + MidiOut *midi=chntodev(chn); + if (midi) midi->chnController(chn,ctl,v); +} +void DeviceManager::sysEx ( uchar *data,ulong size) +{ + for (int i=0;i<n_midi;i++) + device[i]->sysex(data,size); +} + +void DeviceManager::wait (double ticks) +{ +#ifdef HAVE_ALSA_SUPPORT + if (alsa) { ((AlsaOut *)device[default_dev])->wait(ticks); return; }; +#endif + +#ifdef HAVE_OSS_SUPPORT + unsigned long int t=(unsigned long int)(ticks/convertrate); + if (lastwaittime==t) return; + lastwaittime=t; + SEQ_WAIT_TIME(t); + SEQ_DUMPBUF(); +#endif +} + +//void DeviceManager::tmrSetTempo(int v) +void DeviceManager::tmrSetTempo(int v) +{ +#ifdef HAVE_ALSA_SUPPORT + if (alsa) { ((AlsaOut *)device[default_dev])->tmrSetTempo(v); return; } +#endif + +#ifdef HAVE_OSS_SUPPORT + SEQ_SET_TEMPO(v); + SEQ_DUMPBUF(); +#endif +} + +void DeviceManager::tmrStart(long int +#ifdef HAVE_ALSA_SUPPORT +tpcn /*name the argument only if it is used*/ +#endif +) +{ +#ifdef HAVE_ALSA_SUPPORT + if (alsa) { ((AlsaOut *)device[default_dev])->tmrStart(tpcn); return; } +#endif + +#ifdef HAVE_OSS_SUPPORT +#ifdef CONTROLTIMER + if (!timerstarted) + { + SEQ_START_TIMER(); + SEQ_DUMPBUF(); + timerstarted=1; + } + lastwaittime=0; +#else + SEQ_START_TIMER(); + SEQ_DUMPBUF(); +#endif +#endif +} + +void DeviceManager::tmrStop(void) +{ +#ifdef HAVE_ALSA_SUPPORT + if (alsa) { ((AlsaOut *)device[default_dev])->tmrStop(); return; } +#endif + +#ifdef HAVE_OSS_SUPPORT +#ifdef CONTROLTIMER + if (timerstarted) + { + SEQ_STOP_TIMER(); + SEQ_DUMPBUF(); + timerstarted=0; + } +#else + SEQ_STOP_TIMER(); + SEQ_DUMPBUF(); +#endif +#endif +} + +void DeviceManager::tmrContinue(void) +{ +#ifdef HAVE_ALSA_SUPPORT + if (alsa) { ((AlsaOut *)device[default_dev])->tmrContinue(); return; } +#endif + +#ifdef HAVE_OSS_SUPPORT +#ifdef CONTROLTIMER + if (timerstarted) + { + SEQ_CONTINUE_TIMER(); + SEQ_DUMPBUF(); + } +#else + SEQ_CONTINUE_TIMER(); + SEQ_DUMPBUF(); +#endif +#endif +} + +void DeviceManager::sync(bool f) +{ +#ifdef HAVE_ALSA_SUPPORT + if (alsa) { ((AlsaOut *)device[default_dev])->sync(f); return ; }; +#endif + +#ifdef HAVE_OSS_SUPPORT +#ifdef DEVICEMANDEBUG + printf("Sync %d\n",f); +#endif + if (f) + { + seqbuf_clean(); + /* If you have any problem, try removing the next 2 lines, + I though they would be useful here but the may have side effects */ + ioctl(seqfd,SNDCTL_SEQ_RESET); + ioctl(seqfd,SNDCTL_SEQ_PANIC); + } + else + { + seqbuf_dump(); + ioctl(seqfd, SNDCTL_SEQ_SYNC); + }; +#endif +} + +void DeviceManager::seqbuf_dump (void) +{ + if (!alsa) + { +#ifdef HAVE_OSS_SUPPORT + if (_seqbufptr) + { + int r=0; + unsigned char *sb=_seqbuf; + int w=_seqbufptr; + r=write (seqfd, _seqbuf, _seqbufptr); +#ifdef DEVICEMANDEBUG + printf("%d == %d\n",r,w); + printf("%d\n",(errno==EAGAIN)? 1 : 0); +#endif + while (((r == -1)&&(errno==EAGAIN))||(r != w)) + { + if ((r==-1)&&(errno==EAGAIN)) + { + usleep(1); + } + else if ((r>0)&&(r!=w)) + { + w-=r; + sb+=r; + } + r=write (seqfd, sb, w); +#ifdef DEVICEMANDEBUG + printf("%d == %d\n",r,w); + printf("%d\n",(errno==EAGAIN)? 1 : 0); +#endif + } + } + /* + * if (_seqbufptr) + * if (write (seqfd, _seqbuf, _seqbufptr) == -1) + * { + * printf("Error writing to /dev/sequencer in deviceManager::seqbuf_dump\n"); + * perror ("write /dev/sequencer in seqbuf_dump\n"); + * exit (-1); + * } + */ + _seqbufptr = 0; +#endif + } +} + +void DeviceManager::seqbuf_clean(void) +{ +#ifdef HAVE_ALSA_SUPPORT + if (alsa) + ((AlsaOut *)device[default_dev])->seqbuf_clean(); + else +#endif +#ifdef HAVE_OSS_SUPPORT + _seqbufptr=0; +#endif +} + + +const char *DeviceManager::name(int i) +{ +#ifdef HAVE_OSS_SUPPORT + if (checkInit()<0) {_ok = 0; return NULL;} + + if (alsa) + { + if (i<n_midi) return device[i]->deviceName(); + } + else + { + if (i<n_midi) return midiinfo[i].name; + if (i<n_midi+n_synths) return synthinfo[i-n_midi].name; + }; +#endif + return (char *)""; +} + +const char *DeviceManager::type(int i) +{ +#ifdef HAVE_OSS_SUPPORT + if (checkInit()<0) {_ok = 0; return NULL;} + + if (alsa) + { + if (i<n_midi) return "ALSA device"; + } + else + { + if (i<n_midi) + { + return "External Midi Port"; + } + if (i<n_midi+n_synths) + { + switch (synthinfo[i-n_midi].synth_subtype) + { + case (FM_TYPE_ADLIB) : return "Adlib";break; + case (FM_TYPE_OPL3) : return "FM";break; + case (MIDI_TYPE_MPU401) : return "MPU 401";break; + case (SAMPLE_TYPE_GUS) : return "GUS";break; + } + } + } +#endif + return ""; +} + +int DeviceManager::defaultDevice(void) +{ + return default_dev; +} + +void DeviceManager::setDefaultDevice(int i) +{ + if (i>=n_total) return; + default_dev=i; + for (int i=0;i<16;i++) chn2dev[i]=default_dev; +} + +const char *DeviceManager::midiMapFilename(void) +{ + if (device==0L) return ""; + if (default_dev>=n_total) return ""; + return (device[default_dev]!=NULL) ? + device[default_dev]->midiMapFilename() : ""; +} + +void DeviceManager::setMidiMap(MidiMapper *map) +{ + if (map==NULL) return; + mapper_tmp=map; + if (default_dev>=n_total) {default_dev=0;return;}; + if ((device==0L)||(device[default_dev]==NULL)) + return; + device[default_dev]->setMidiMapper(map); +} + +int DeviceManager::setPatchesToUse(int *patchesused) +{ + if (checkInit()<0) return -1; + if ((device==0L)||(device[default_dev]==NULL)) + return 0; + + if ((device[default_dev]->deviceType())==KMID_GUS) + { + GUSOut *gus=(GUSOut *)device[default_dev]; + gus->setPatchesToUse(patchesused); + } + return 0; +} + +void DeviceManager::setVolumePercentage(int v) +{ + if (device!=0L) + { + for (int i=0;i<n_total;i++) + { + device[i]->setVolumePercentage(v); + } + } +} + +void DeviceManager::setDeviceNumberForChannel(int chn, int dev) +{ + chn2dev[chn]=dev; +} + +void DeviceManager::allNotesOff(void) +{ + for (int i=0;i<n_midi;i++) + device[i]->allNotesOff(); +} diff --git a/libkmid/deviceman.h b/libkmid/deviceman.h new file mode 100644 index 000000000..463bbfba7 --- /dev/null +++ b/libkmid/deviceman.h @@ -0,0 +1,537 @@ +/* deviceman.h - The device manager, that hides the use of midiOut + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef DEVICEMAN_H +#define DEVICEMAN_H + +#include <libkmid/dattypes.h> +#include <kdelibs_export.h> + +class MidiOut; +class MidiMapper; + +/** + * MIDI Device Manager class . This class is the one you should use to + * send MIDI events to any device, as it creates and manages the *Out classes. + * + * This class is usually used by creating a DeviceManager object, then call + * openDev() and initDev() . Then, use numberOfMidiPorts(), + * numberOfSynthDevices(), name() and type() to choose which + * device to play MIDI events to and then use defaultDevice() to set the + * MIDI device to play. + * + * @short Manages all MIDI devices and redirects MIDI events to each one as + * configured. + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class KMID_EXPORT DeviceManager +{ + protected: + + /** + * @internal + * The midi devices objects + */ + MidiOut **device; + + /** + * @internal + * Midi info + */ + struct midi_info *midiinfo; + + /** + * @internal + * Synth info + */ + struct synth_info *synthinfo; + + /** + * @internal + * Stores the device thru which a channel will be sent + */ + int chn2dev[16]; + + /** + * @internal + * Number of synths devices + */ + int n_synths; + + /** + * @internal + * Number of midi ports + */ + int n_midi; + + /** + * @internal + * n_midi + n_synths + */ + int n_total; + + /** + * @internal + * rate + */ + int m_rate; + + /** + * @internal + * A "constant" used to convert from milliseconds to the computer rate. + */ + double convertrate; + + /** + * @internal + * Newest kernels don't want me to stop a timer that hasn't been started :-) + */ + int timerstarted; + + /** + * @internal + * Last time waited for in wait(double) + */ + double lastwaittime; + + /** + * @internal + * Keeps a pointer to the mapper so that if devices weren't initialized when + * first called setMidiMap then, when they get initialized, they use the + * proper mapper + */ + MidiMapper *mapper_tmp; + + int initialized; + + /** + * @internal + * The real file handler for /dev/sequencer, that is opened and closed. + */ + int seqfd; + + /** + * @internal + * The device to which timer events will be sent + */ + int default_dev; + + /** + * @internal + */ + int _ok; + + /** + * @internal + * True if the user is running ALSA. False if (s)he's using OSS + */ + bool alsa; + + /** + * @internal + */ + void seqbuf_dump (void); + + /** + * @internal + */ + void seqbuf_clean (void); + + /** + * @internal + */ + void checkAlsa (void); + public: + /** + * Constructor. It just initializes internal variables, before playing any + * music, you should call initManager(), setMidiMap() + * (optional), openDev(), initDev(), setPatchesToUse() + * (not required, unless you're playing to a GUS device, which must load + * the patches), tmrStart(), and finally, play the music. + */ + DeviceManager(int def=-1); + + /** + * Destructor. It closes the device (calling closeDev() ) if it wasn't + * closed before. + */ + ~DeviceManager(void); + + /** + * Initializes the MIDI Device Manager object. + * + * The /dev/sequencer and/or /dev/snd/seq files are opened, available + * devices are analyzed and *Out objects are created. Then, the + * device files are closed. + * + * @return 0 if everything was OK, or -1 if there was an error and it + * couldn't be initialized (for example, because it couldn't open the + * /dev/sequencer file) + */ + int initManager(void); + + /** + * Checks if the device manager has been initialized (with @p initManager), + * and in case it wasn't, initializes it. + * + * @return 0 if it was (or has just been) correctly initialized, and -1 if + * there was an error. + */ + int checkInit(void); + + /** + * \obsolete Please use deviceForChannel() instead. + * + */ + MidiOut *chntodev(int chn) + { return deviceForChannel(chn); } + + /** + * It's possible to send different MIDI channels to different MIDI devices, + * so that you can for example send channel 1 to an external synthesizer, + * channel 2 to a FM device and channel 10 to an AWE synth. + * + * @return the device to which MIDI events goind to channel @p chn should + * be sent. + */ + MidiOut *deviceForChannel(int chn) + { return (device!=0L) ? device[chn2dev[chn]] : 0L ; } + + /** + * Returns the device number associated with a given channel. + */ + int deviceNumberForChannel(int chn) { return chn2dev[chn]; } + + /** + * Sets the device number associated with a given channel. + */ + void setDeviceNumberForChannel(int chn, int dev); + + /** + * @return 0 if there was a problem and 1 if everything was OK. Note that the + * return value is changed after you check it, so you can only check it once. + */ + int ok(void); + + /** + * Returns true if it's running ALSA and false if OSS is being run + */ + int usingAlsa(void) { return alsa; } + + // The following function are here to emulate a midi, so that the + // DeviceManager sends the events to the appropriate devices. + + /** + * Open the devices. It first initializes the manager it that wasn't done + * yet (you should do it yourself, to be able to choose the MIDI output + * device, as it will be set to an external synth by default, if available). + * + * Then /dev/sequencer is opened and the MIDI devices are opened + * (calling MidiOut::openDev() ). + * @see ok() to check if there was any problem + * @see closeDev() + * @see initDev() + */ + void openDev (void); + + /** + * Closes the devices, and /dev/sequencer. + * + * @see openDev() + */ + void closeDev (void); + + /** + * Calls MidiOut::initDev() in turn in each of the available devices. + * + * @see MidiOut::initDev() + */ + void initDev (void); + + /** + * Sends a Note On MIDI event. + * + * @param chn the MIDI channel (0 to 15) to play the note on. + * @param note the key of the note to play (0 to 127). + * @param vel the velocity of the note (0 to 127). + * + * @see noteOff() + */ + void noteOn ( uchar chn, uchar note, uchar vel ); + + /** + * Sends a Note Off MIDI event. This is equivalent to send a Note On event + * with a vel value of 0. + * + * @param chn the MIDI channel (0 to 15) to play the note on. + * @param note the key of the note to play (0 to 127). + * @param vel the velocity of the note (0 to 127). + * + * @see noteOn() + */ + void noteOff ( uchar chn, uchar note, uchar vel ); + + /** + * Sends a Key Pressure (or Aftertouch) MIDI event. + * This event changes the pressure over a key after this key has been played. + * + * @param chn the MIDI channel (0 to 15) where the note is being played. + * @param note the key of the note (0 to 127). + * @param vel the new velocity (or pressure) of the note (0 to 127). + */ + void keyPressure ( uchar chn, uchar note, uchar vel ); + + /** + * Changes the patch (instrument) on a MIDI channel. + * + * @see setPatchesToUse() + * + * @param chn the MIDI channel (0 to 15) . + * @param patch the General Midi patch (0 to 127) to use on the channel chn. + */ + void chnPatchChange ( uchar chn, uchar patch ); + + /** + * Changes the Pressure (Aftertouch) on a MIDI channel. Keep in mind that + * some synthesizers don't like this events, and it's better not to send it. + * + * @param chn the MIDI channel (0 to 15) to change. + * @param vel the velocity (0 to 127) to use on the channel chn. + */ + void chnPressure ( uchar chn, uchar vel ); + + /** + * Changes the Pitch Bender value on a MIDI channel. This bends the tone of + * each note played on this channel. + * + * @param chn the MIDI channel (0 to 15) to use. + * @param lsb and @p msb the less significant byte and the most significant + * byte (0 to 127 each) of the number by which notes will be bend. a 0x4000 + * value means not to bend. + * @param msb the most significant byte + */ + void chnPitchBender ( uchar chn, uchar lsb, uchar msb ); + + /** + * Sends a Controller event to a MIDI channel. This can be used for example + * to change the volume, set a XG patch, etc. Look for any General Midi + * resource page on the net for more information about the available + * controller events. + * + * For example, to set the tremolo value to a maximum on the MIDI channel + * number one, you should pass 1 to @p chn, 1 to @p ctl and 127 to @p v. + * + * @param chn the MIDI channel (0 to 15) to send the event to. + * @param ctl the controller (0 to 15) to send. + * @param v the value (data) of the controller. + */ + void chnController ( uchar chn, uchar ctl , uchar v ); + + /** + * Sends a SYStem EXclusive message to the default MIDI device (usually, + * external MIDI synths, as most internal synths do not support sysex + * messages) + * + * @param data the array of bytes that comform the system exclusive message. + * Without the initial 0xF0 char, and including the final 0xF7 char (end of + * exclusive message) + * @param size the size in bytes of the data to send + * + * @see setDefaultDevice() + */ + void sysEx ( uchar *data,ulong size); + + /** + * Sets the number of milliseconds at which the next event will be sent. + * This way, you can schedule notes and events to send to the MIDI device. + * @see tmrStart() + */ + void wait (double ms); + + /** + * Sets the tempo which will be used to convert between ticks and + * milliseconds. + */ + void tmrSetTempo(int v); + + /** + * Starts the timer. You must call tmrStart before using wait() + */ + void tmrStart(long int tpcn); + + /** + * Stops the timer. This will be called by closeDev() before closing + * the device + */ + void tmrStop(void); + + /** + * Continue the stopped timer . It is the same than starting a new timer, but + * without resetting it. + */ + void tmrContinue(void); + + /** + * Sends an all notes off event + */ + void allNotesOff(void); + + /** + * Synchronizes with the MIDI buffer. Midi events are put into a buffer, + * along with timer delays (see wait() ). sync returns when the buffer + * is empty. + * + * @param f if false, it syncronizes by waiting for the buffer to be sent. + * If true, it forces the synchronization by clearing the buffer + * inmediately. The "force" method is, of course, not recommended, except + * in rare situations. + */ + void sync(bool f=0); + + /** + * Changes the "master" volume of the played events by altering next volume + * controller events. The parameter @p i should be in the range of 0 + * (nothing is heard) to 150 (music is played at a 150% of the original + * volume). + * + * Keep in mind that as most MIDI files already play music at near the + * maximum volume, an @p i value greater than 100 is very probably ignored + * most of the times. + */ + void setVolumePercentage(int i); + + /** + * Returns the device to which the MIDI events will be sent. + * Returns -1 if there's no available device. + * + * @see setDefaultDevice() + */ + int defaultDevice(void); + + /** + * Sets the device to send the MIDI events to. + * + * By using midiPorts(), synthDevices(), name() and + * type(), you should choose which device to use (note that they are + * numbered with midi ports being first and synth devices next) + * + * @see defaultDevice() + */ + void setDefaultDevice(int i); + + /** + * Loads the patches you're going to use . This has effect only for GUS + * cards, although, if you use this function when defaultDevice() is + * not a GUS device, it will be ignored. + * + * The parameter is an int [256] array, which contain the following: + * + * The first 0..127 integers, are the number of times each General MIDI patch + * will be used, and -1 when the corresponding patch won't be used. + * + * The 128..255 integers are the number of times each drum voice (each note + * on the drum channel) will be used, and -1 when the corresponding + * percussion won't be used. + * + * This is done this way so that if the user has very little memory on his + * GUS card, and not all patches will be loaded, they are at least + * reordered, so that it first loads the one you're going to use most. + * + * In case you don't worry about such users, or you don't know "a priori" + * the number of notes you're going to play, you can just use 1 for each + * patch you want to load and -1 in the rest. + * + * @see GUSOut::setPatchesToUse() + * @see GUSOut::loadPatch() + * + * @return 0 if ok, and -1 if there wasn't enough memory to load the patches + * in the card's memory. + */ + int setPatchesToUse(int *patchesused); + + /** + * Returns the filename where the Midi Mapper was loaded from, or "" if no + * MIDI Mapper is in use. + * + * @see setMidiMap() + */ + const char *midiMapFilename(void); + + /** + * Sets a MidiMapper object to use. This object should already have + * loaded the configuration. See the description of MidiMapper for + * more information. + * + * @see MidiMapper::MidiMapper() + * @see midiMapFilename() + */ + void setMidiMap(MidiMapper *map); + + /** + * Returns the SNDCTL_SEQ_CTRLRATE ioctl value + */ + int rate(void) { return m_rate; } + + /** + * Returns the number of MIDI ports available on the system. It's common that + * users have MIDI ports available, but there are no external synthesizers + * connected to these ports, so sending MIDI events to these ports will not + * produce any music in this case. + * + * @see synthDevices() + * @see setDefaultDevice() + */ + int midiPorts(void) { return n_midi; } + + /** + * Returns the number of internal synthesizers available on the system. Some + * of these devices will need special configuration, for example, to load + * sound patches. + * + * @see midiPorts() + * @see setDefaultDevice() + * @see setPatchesToUse() + */ + int synthDevices(void) { return n_synths; } + + /** + * Returns the name of the @p i-th device . In case the DeviceManager wasn't + * yet initialized ( see checkInit() ), the return value is NULL, and + * in case the parameter has a value out of the valid range ( 0 to + * midiPorts() + synthDevices() ) it returns an empty string. + */ + const char *name(int i); + + /** + * Returns the type of device the @p i-th device is , in a user-friendly + * string . For example, "External Midi Port" for midi ports, "FM" for FM + * synthesizers, "GUS" for Gravis Ultrasound devices, etc. + */ + const char *type(int i); + + private: + class DeviceManagerPrivate; + DeviceManagerPrivate *d; +}; + +#endif diff --git a/libkmid/fmout.cc b/libkmid/fmout.cc new file mode 100644 index 000000000..c4af93dcd --- /dev/null +++ b/libkmid/fmout.cc @@ -0,0 +1,354 @@ +/************************************************************************** + + fmout.cc - class fmOut which handles the /dev/sequencer device + for fm synths + This file is part of LibKMid 0.9.5 + Copyright (C) 1998,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "fmout.h" +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include "sndcard.h" +#include <sys/ioctl.h> +#include <errno.h> +#include <string.h> +#include <sys/param.h> +#include <stdlib.h> +#include <limits.h> +#include "midispec.h" +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +SEQ_USE_EXTBUF(); + +FMOut::FMOut( int d, int total ) +{ + seqfd = -1; + devicetype = KMID_FM; + device = d; + _ok = 1; + // Put opl=3 for opl/3 (better quality/ 6 voices) + // or opl=2 for fm output (less quality/ 18 voices, which is better imho) : + opl = 2; + // But be aware that opl=3 is not intended to be fully supported by now + + nvoices = total; + vm = new VoiceManager (nvoices); +} + +FMOut::~FMOut() +{ + closeDev(); + delete vm; + if (deleteFMPatchesDirectory) + { + free((char *)FMPatchesDirectory); + deleteFMPatchesDirectory = 0; + FMPatchesDirectory="/etc"; + } +} + +void FMOut::openDev (int sqfd) +{ +#ifdef HAVE_OSS_SUPPORT + _ok=1; + seqfd = sqfd; + //vm->clearLists(); + if ( seqfd == -1 ) + { + printfdebug("ERROR: Could not open /dev/sequencer\n"); + return; + } + + loadFMPatches(); +#endif + +} + +void FMOut::closeDev (void) +{ + if (!ok()) return; + vm->clearLists(); + //if (seqfd>=0) close(seqfd); + seqfd = -1; +} + +void FMOut::initDev (void) +{ +#ifdef HAVE_OSS_SUPPORT + int chn; + if (!ok()) return; + uchar gm_reset[5]={0x7e, 0x7f, 0x09, 0x01, 0xf7}; + sysex(gm_reset, sizeof(gm_reset)); + for (chn=0;chn<16;chn++) + { + chnmute[chn]=0; + chnPatchChange(chn,0); + chnPressure(chn,127); + chnPitchBender(chn, 0x00, 0x40); + chnController(chn, CTL_MAIN_VOLUME,127); + chnController(chn, CTL_EXT_EFF_DEPTH, 0); + chnController(chn, CTL_CHORUS_DEPTH, 0); + chnController(chn, 0x4a, 127); + } + + if (opl==3) ioctl(seqfd, SNDCTL_FM_4OP_ENABLE, &device); + SEQ_VOLUME_MODE(device,VOL_METHOD_LINEAR); + + for (int i = 0; i < nvoices; i++) + { + SEQ_CONTROL(device, i, SEQ_VOLMODE, VOL_METHOD_LINEAR); + SEQ_STOP_NOTE(device, i, vm->note(i), 64); + } +#endif +} + +void FMOut::loadFMPatches(void) +{ +#ifdef HAVE_OSS_SUPPORT + char patchesfile[PATH_MAX]; + char drumsfile[PATH_MAX]; + int size; + struct sbi_instrument instr; + char tmp[60]; + int i,j; + for ( i=0; i<256; i++ ) + patchloaded[i] = 0; + int stereoeffect=rand()%3; + FILE *fh; + int datasize; + + if (opl==3) + { + snprintf(patchesfile, PATH_MAX, "%s/std.o3",FMPatchesDirectory); + size=60; + } + else + { + snprintf(patchesfile, PATH_MAX, "%s/std.sb",FMPatchesDirectory); + size=52; + } + fh=fopen(patchesfile,"rb"); + if (fh==NULL) return; + + for (i=0;i<128;i++) + { + fread(tmp,size,1,fh); + patchloaded[i]=1; + instr.key = ((strncmp(tmp, "4OP", 3) == 0))? OPL3_PATCH : FM_PATCH; + datasize = (strncmp(tmp, "4OP", 3) == 0)? 22 : 11; + instr.device=device; + instr.channel = i; + // Let's get some stereo effect ... + tmp[46] = (tmp[46] & 0xcf) | ((++stereoeffect)<<4); + stereoeffect=stereoeffect%3; + for (j=0; j<22; j++) + instr.operators[j] = tmp[j+36]; + SEQ_WRPATCH(&instr,sizeof(instr)); + } + fclose(fh); + + if (opl==3) + { + snprintf(drumsfile, PATH_MAX, "%s/drums.o3",FMPatchesDirectory); + } + else + { + snprintf(drumsfile, PATH_MAX, "%s/drums.sb",FMPatchesDirectory); + } + + fh=fopen(drumsfile,"rb"); + if (fh==NULL) return; + + for (i=128;i<175;i++) + { + fread(tmp,size,1,fh); + patchloaded[i]=1; + instr.key = (strncmp(tmp, "4OP", 3) == 0)? OPL3_PATCH : FM_PATCH; + datasize = (strncmp(tmp, "4OP", 3) == 0)? 22 : 11; + instr.device=device; + instr.channel = i; + // Let's get some stereo effect ... + tmp[46] = (tmp[46] & 0xcf) | ((++stereoeffect)<<4); + stereoeffect=stereoeffect%3; + for (j=0; j<22; j++) + instr.operators[j] = tmp[j+36]; + SEQ_WRPATCH(&instr,sizeof(instr)); + } + fclose(fh); + +#ifdef FMOUTDEBUG + printfdebug("Patches loaded\n"); +#endif +#endif +} + +int FMOut::patch(int p) +{ + if (patchloaded[p]==1) return p; +#ifdef FMOUTDEBUG + printfdebug("Not loaded %d!\n",p); +#endif + p=0; + while ((p<256)&&(patchloaded[p]==0)) p++; + return p; +} + +void FMOut::noteOn (uchar chn, uchar note, uchar vel) +{ + if (vel==0) + { + noteOff(chn,note,vel); + } + else + { + if (chn==PERCUSSION_CHANNEL) + { + if (patchloaded[note+128]==0) return; + else + if (patchloaded[chnpatch[chn]]==0) return; + } + int v=vm->allocateVoice(chn,note); + int p; + if (chn==PERCUSSION_CHANNEL) + SEQ_SET_PATCH(device,v ,p=patch(note+128)) + else + SEQ_SET_PATCH(device,v ,p=map->patch(chn,chnpatch[chn])); + SEQ_BENDER(device, v, chnbender[chn]); + + SEQ_START_NOTE(device, v, note, vel); + // SEQ_CONTROL(device, v, CTL_MAIN_VOLUME, chncontroller[chn][CTL_MAIN_VOLUME]); + + SEQ_CHN_PRESSURE(device, v , chnpressure[chn]); + } + +#ifdef FMOUTDEBUG + printfdebug("Note ON >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +void FMOut::noteOff (uchar chn, uchar note, uchar vel) +{ + int i; + vm->initSearch(); + while ((i=vm->search(chn,note))!=-1) + { + SEQ_STOP_NOTE(device, i, note, vel); + vm->deallocateVoice(i); + } + +#ifdef FMOUTDEBUG + printfdebug("Note OFF >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +void FMOut::keyPressure (uchar chn, uchar note, uchar vel) +{ + int i; + vm->initSearch(); + while ((i=vm->search(chn,note))!=-1) + SEQ_KEY_PRESSURE(device, i, note,vel); +} + +void FMOut::chnPatchChange (uchar chn, uchar patch) +{ + if (chn==PERCUSSION_CHANNEL) return; + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_SET_PATCH(device,i,map->patch(chn,patch)); + + chnpatch[chn]=patch; +} + +void FMOut::chnPressure (uchar chn, uchar vel) +{ + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_CHN_PRESSURE(device, i , vel); + + chnpressure[chn]=vel; +} + +void FMOut::chnPitchBender(uchar chn,uchar lsb, uchar msb) +{ + chnbender[chn]=((int)msb<<7) | (lsb & 0x7F); + + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_BENDER(device, i, chnbender[chn]); + +} + +void FMOut::chnController (uchar chn, uchar ctl, uchar v) +{ + if ((ctl==11)||(ctl==7)) + { + v=(v*volumepercentage)/100; + if (v>127) v=127; + } + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_CONTROL(device, i, ctl, v); + + chncontroller[chn][ctl]=v; +} + +void FMOut::sysex(uchar *, ulong ) +{ + +} + +void FMOut::setFMPatchesDirectory(const char *dir) +{ + if ((dir==NULL)||(dir[0]==0)) return; + if (deleteFMPatchesDirectory) + free((char *)FMPatchesDirectory); + + FMPatchesDirectory = strdup(dir); + + deleteFMPatchesDirectory=1; +} + +void FMOut::setVolumePercentage ( int i ) +{ +#ifdef HAVE_OSS_SUPPORT + int fd=open("/dev/mixer0",O_RDWR,0); + if (fd==-1) return; + int a=i*255/100; + if (a>255) a=255; + a=(a<<8) | a; + if (ioctl(fd,MIXER_WRITE(SOUND_MIXER_SYNTH),&a) == -1) + printfdebug("ERROR writing to mixer\n"); + close(fd); +#endif + volumepercentage=i; +} + + +const char *FMOut::FMPatchesDirectory = "/etc"; +int FMOut::deleteFMPatchesDirectory = 0; diff --git a/libkmid/fmout.h b/libkmid/fmout.h new file mode 100644 index 000000000..328ca5626 --- /dev/null +++ b/libkmid/fmout.h @@ -0,0 +1,155 @@ +/* fmout.h - class fmOut which handles the /dev/sequencer device + for FM synths + This file is part of LibKMid 0.9.5 + Copyright (C) 1998,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _FMOUT_H +#define _FMOUT_H + +#include <libkmid/midiout.h> +#include <libkmid/voiceman.h> + +/** + * FM device output class . FMOut is used to send MIDI events to + * FM devices, such as AdLib cards, or OPL3 synthesizers. + * + * FMOut inherits MidiOut and supports the same simple API. + * + * The preferred way to use this class is by selecting a FM device + * on the MidiManager and using a MidiManager object directly + * + * @short Sends MIDI events to FM devices + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class KMID_EXPORT FMOut : public MidiOut +{ + private: + class FMOutPrivate; + FMOutPrivate *di; + + int patchloaded[256]; + /** + * Takes a value of 2 or 3, for FM or OPL3 support + */ + int opl; + int nvoices; + + VoiceManager *vm; + + void modifyPatch(char *buf, int key); + void loadFMPatches (void); + + public: + /** + * Constructor. See MidiOut::MidiOut() for more information. + */ + FMOut ( int d=0, int total =12 ); + + /** + * Destructor. + */ + ~FMOut (); + + /** + * See MidiOut::openDev() + */ + virtual void openDev ( int sqfd ); + + /** + * See MidiOut::closeDev() + */ + virtual void closeDev ( void ); + + /** + * See MidiOut::initDev() + */ + virtual void initDev ( void ); + + /** + * See MidiOut::noteOn() + */ + virtual void noteOn ( uchar chn, uchar note, uchar vel ); + + /** + * See MidiOut::noteOff() + */ + virtual void noteOff ( uchar chn, uchar note, uchar vel ); + + /** + * See MidiOut::keyPressure() + */ + virtual void keyPressure ( uchar chn, uchar note, uchar vel ); + + /** + * See MidiOut::chnPatchChange() + */ + virtual void chnPatchChange ( uchar chn, uchar patch ); + + /** + * See MidiOut::chnPressure() + */ + virtual void chnPressure ( uchar chn, uchar vel ); + + /** + * See MidiOut::chnPitchBender() + */ + virtual void chnPitchBender ( uchar chn, uchar lsb, uchar msb ); + + /** + * See MidiOut::chnController() + */ + virtual void chnController ( uchar chn, uchar ctl , uchar v ); + + /** + * It's an empty function, as FM devices don't support System Exclusive + * messages + */ + virtual void sysex ( uchar *data,ulong size); + + /** + * See MidiOut::setVolumePercentage() + */ + virtual void setVolumePercentage ( int i ); + + /** + * Returns @p p if the patch p has been loaded, or another patch (already loaded) + * if @p p hasn't been loaded. + */ + int patch(int p); + + private: + static const char *FMPatchesDirectory; + static int deleteFMPatchesDirectory; + + public: + /** + * Sets the directory where the FM patches are stored, that is, where the + * std.o3, std.sb, drums.o3 and drums.sb files can be found. + * + * It will store a copy of the parameter, so you should delete the memory + * used by the parameter you passed. + */ + static void setFMPatchesDirectory(const char *dir); + +}; + +#endif diff --git a/libkmid/gusout.cc b/libkmid/gusout.cc new file mode 100644 index 000000000..6b207956b --- /dev/null +++ b/libkmid/gusout.cc @@ -0,0 +1,691 @@ +/************************************************************************** + + gusout.cc - class GUSOut which implements support for Gravis + Ultrasound cards through a /dev/sequencer device + This file is part of LibKMid 0.9.5 + Copyright (C) 1998,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "gusout.h" +#include "sndcard.h" +#include "midispec.h" +#include "gusvoices.h" +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/ioctl.h> +#include <errno.h> +#include <string.h> +#include <sys/param.h> +#include <stdlib.h> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +SEQ_USE_EXTBUF(); + +#ifdef HAVE_OSS_SUPPORT +struct pat_header +{ + char magic[12]; + char version[10]; + char description[60]; + unsigned char instruments; + char voices; + char channels; + unsigned short nr_waveforms; + unsigned short master_volume; + unsigned long data_size; +}; +struct sample_header +{ + char name[7]; + unsigned char fractions; + long len; + long loop_start; + long loop_end; + unsigned short base_freq; + long low_note; + long high_note; + long base_note; + short detune; + unsigned char panning; + + unsigned char envelope_rate[6]; + unsigned char envelope_offset[6]; + + unsigned char tremolo_sweep; + unsigned char tremolo_rate; + unsigned char tremolo_depth; + + unsigned char vibrato_sweep; + unsigned char vibrato_rate; + unsigned char vibrato_depth; + + char modes; + + short scale_frequency; + unsigned short scale_factor; +}; + +int get_dint(unsigned char *p) +{ + unsigned int v=0; + + for (int i=0;i<4;i++) + { + v |= (p[i] << (i*8)); + } + return (int)v; +} + +unsigned short get_word(unsigned char *p) +{ + unsigned short v=0; + + for (int i=0;i<2;i++) + v |= (*p++ << (i*8)); + return (short)v; +} + +#endif + +GUSOut::GUSOut(int d,int total) +{ + seqfd = -1; + devicetype=KMID_GUS; + device= d; + _ok=1; + + use8bit=0; + nvoices=total; + vm=new VoiceManager(nvoices); +} + +GUSOut::~GUSOut() +{ + closeDev(); + + delete vm; + if (delete_GUS_patches_directory) + { + free((char *)GUS_patches_directory); + delete_GUS_patches_directory = 0; + GUS_patches_directory="/etc"; + } +} + +void GUSOut::openDev (int sqfd) +{ + _ok=1; + seqfd = sqfd; + //vm->clearLists(); + if (seqfd==-1) + { + printfdebug("ERROR: Could not open /dev/sequencer\n"); + return; + } + +#ifdef HAVE_OSS_SUPPORT + + //seqbuf_clean(); + //ioctl(seqfd,SNDCTL_SEQ_RESET); + //ioctl(seqfd,SNDCTL_SEQ_PANIC); + + if (ioctl(seqfd, SNDCTL_SEQ_RESETSAMPLES, &device)==-1) + { + printfdebug("Error reseting gus samples. Please report\n"); + }; + use8bit=0; + totalmemory = device; + ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &totalmemory); + freememory = device; + ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &freememory); + +#endif + + +} + +void GUSOut::closeDev (void) +{ + if (!ok()) return; + vm->clearLists(); + //if (seqfd>=0) + // close(seqfd); + seqfd=-1; +} + +void GUSOut::initDev (void) +{ +#ifdef HAVE_OSS_SUPPORT + int chn; + if (!ok()) return; + uchar gm_reset[5]={0x7e, 0x7f, 0x09, 0x01, 0xf7}; + sysex(gm_reset, sizeof(gm_reset)); + for (chn=0;chn<16;chn++) + { + chnmute[chn]=0; + chnPatchChange(chn,0); + // chnPressure(chn,127); + chnPitchBender(chn, 0x00, 0x40); + chnController(chn, CTL_MAIN_VOLUME,127); + chnController(chn, CTL_EXT_EFF_DEPTH, 0); + chnController(chn, CTL_CHORUS_DEPTH, 0); + chnController(chn, 0x4a, 127); + } + + + for (int i = 0; i < nvoices; i++) + { + SEQ_CONTROL(device, i, SEQ_VOLMODE, VOL_METHOD_LINEAR); + SEQ_STOP_NOTE(device, i, vm->note(i), 64); + } + +#endif +} + + +int GUSOut::patch(int p) +{ + if (patchloaded[p]==1) return p; + printfdebug("Not loaded %d!\n",p); + p=0; + while ((p<256)&&(patchloaded[p]==0)) p++; + return p; +} + +void GUSOut::noteOn (uchar chn, uchar note, uchar vel) +{ + if (vel==0) + { + noteOff(chn,note,vel); + } + else + { + if (chn==PERCUSSION_CHANNEL) + { + if (patchloaded[note+128]==0) return; + else + if (patchloaded[chnpatch[chn]]==0) return; + }; + int v=vm->allocateVoice(chn,note); + int p; + if (chn==PERCUSSION_CHANNEL) + SEQ_SET_PATCH(device,v ,p=patch(note+128)) + else + SEQ_SET_PATCH(device,v ,p=map->patch(chn,chnpatch[chn])); + SEQ_BENDER(device, v, chnbender[chn]); + + SEQ_START_NOTE(device, v, note, vel); + // SEQ_CONTROL(device, v, CTL_MAIN_VOLUME, chncontroller[chn][CTL_MAIN_VOLUME]); + SEQ_CHN_PRESSURE(device, v , chnpressure[chn]); + } + + printfdebug("Note ON >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +} + +void GUSOut::noteOff (uchar chn, uchar note, uchar vel) +{ + int i; + vm->initSearch(); + while ((i=vm->search(chn,note))!=-1) + { + SEQ_STOP_NOTE(device, i, note, vel); + vm->deallocateVoice(i); + } + +#ifdef GUSOUTDEBUG + printf("Note OFF >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +void GUSOut::keyPressure (uchar chn, uchar note, uchar vel) +{ + int i; + vm->initSearch(); + while ((i=vm->search(chn,note))!=-1) + SEQ_KEY_PRESSURE(device, i, note,vel); +} + +void GUSOut::chnPatchChange (uchar chn, uchar patch) +{ + if (chn==PERCUSSION_CHANNEL) return; + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_SET_PATCH(device,i,map->patch(chn,patch)); + chnpatch[chn]=patch; + +} + +void GUSOut::chnPressure (uchar /*chn*/, uchar /*vel*/) +{ + /* int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_CHN_PRESSURE(device, i , vel); + chnpressure[chn]=vel; + */ +} + +void GUSOut::chnPitchBender(uchar chn,uchar lsb, uchar msb) +{ + chnbender[chn]=((int)msb<<7) | (lsb & 0x7F); + + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_BENDER(device, i, chnbender[chn]); +} + +void GUSOut::chnController (uchar chn, uchar ctl, uchar v) +{ + if ((ctl==11)||(ctl==7)) + { + v=(v*volumepercentage)/100; + if (v>127) v=127; + }; + + int i; + vm->initSearch(); + while ((i=vm->search(chn))!=-1) + SEQ_CONTROL(device, i, ctl, v); + + chncontroller[chn][ctl]=v; +} + +void GUSOut::sysex(uchar *, ulong ) +{ + +} + +void GUSOut::setGUSPatchesDirectory(const char *dir) +{ + if ((dir==NULL)||(dir[0]==0)) return; + if (delete_GUS_patches_directory) + free((char *)GUS_patches_directory); + + GUS_patches_directory = strdup(dir); + delete_GUS_patches_directory=1; +} + +const char *GUSOut::patchName(int pgm) +{ + return GUS_voice_names[pgm]; +} + + +int GUSOut::loadPatch(int pgm) +{ +#ifdef HAVE_OSS_SUPPORT + struct pat_header header; + struct sample_header sample; + if (patchloaded[pgm]==1) + { +#ifdef GUSOUTDEBUG + printf("Trying to reload a patch. This should never happen, please report.\n"); +#endif + return 0; + } + if ((patchName(pgm)==NULL)||((patchName(pgm))[0]==0)) + { +#ifdef GUSOUTDEBUG + printf("Couldn't guess patch name for patch number %d\n",pgm); +#endif + return -1; + } + char *s=new char[strlen(GUS_patches_directory)+strlen(patchName(pgm))+10]; + if (s==NULL) return -1; + sprintf(s,"%s/%s.pat",GUS_patches_directory,patchName(pgm)); +#ifdef GUSOUTDEBUG + printf("Loading patch : %s\n",s); +#endif + struct patch_info *patch=NULL; + struct stat info; + if (stat(s, &info)==-1) + { +#ifdef GUSOUTDEBUG + printf("File %s doesn't exist\n",s); +#endif + return -1; + } + + FILE *fh=fopen(s,"rb"); + if (fh==NULL) + { +#ifdef GUSOUTDEBUG + printf("Couldn't open patch %s\n",s); +#endif + return -1; + } + + unsigned char tmp[256]; + if (fread(tmp,1,0xef,fh)!=0xef) + { + fclose(fh); +#ifdef GUSOUTDEBUG + printf("Short file ! \n"); +#endif + return -1; + } + memcpy ((char *) &header, tmp, sizeof (header)); + + if (strncmp(header.magic,"GF1PATCH110",12)!=0) + { +#ifdef GUSOUTDEBUG + printf("File %s is corrupted or it isn't a patch file\n",s); +#endif + return -1; + } + if (strncmp(header.version,"ID#000002",10)!=0) + { +#ifdef GUSOUTDEBUG + printf("File %s's version is not supported\n",s); +#endif + return -1; + } + unsigned short nWaves= *(unsigned short *)&tmp[85]; +#ifdef GUSOUTDEBUG + unsigned short masterVolume= *(unsigned short *)&tmp[87]; + printf("nWaves: %d\n",nWaves); + printf("masterVolume : %d\n",masterVolume); +#endif + + unsigned short i; + int offset=0xef; + for (i=0;i<nWaves;i++) + { + fseek(fh,offset,SEEK_SET); + + if (fread(tmp,1,sizeof(sample),fh) != sizeof(sample)) + { + fclose(fh); +#ifdef GUSOUTDEBUG + printf("Short file\n"); +#endif + return -1; + } + memcpy ((char *) &sample, tmp, sizeof (sample)); + sample.fractions = (char)tmp[7]; + sample.len = get_dint(&tmp[8]); + sample.loop_start = get_dint(&tmp[12]); + sample.loop_end = get_dint(&tmp[16]); + sample.base_freq = get_word(&tmp[20]); + sample.low_note = get_dint(&tmp[22]); + sample.high_note = get_dint(&tmp[26]); + sample.base_note = get_dint(&tmp[30]); + sample.detune = (short)get_word(&tmp[34]); + sample.panning = (unsigned char) tmp[36]; + + memcpy (sample.envelope_rate, &tmp[37], 6); + memcpy (sample.envelope_offset, &tmp[43], 6); + + sample.tremolo_sweep = (unsigned char) tmp[49]; + sample.tremolo_rate = (unsigned char) tmp[50]; + sample.tremolo_depth = (unsigned char) tmp[51]; + + sample.vibrato_sweep = (unsigned char) tmp[52]; + sample.vibrato_rate = (unsigned char) tmp[53]; + sample.vibrato_depth = (unsigned char) tmp[54]; + sample.modes = (unsigned char) tmp[55]; + sample.scale_frequency = (short)get_word(&tmp[56]); + sample.scale_factor = get_word(&tmp[58]); + + offset = offset + 96; + + patch = (struct patch_info *) malloc(sizeof (*patch) + sample.len); + if (patch == NULL) + { +#ifdef GUSOUTDEBUG + printf("Not enough memory\n"); +#endif + return -1; + } + patch->key = GUS_PATCH; + patch->device_no = device; + patch->instr_no = pgm; + patch->mode = sample.modes | WAVE_TREMOLO | WAVE_VIBRATO | WAVE_SCALE; + patch->len = sample.len; + patch->loop_start = sample.loop_start; + patch->loop_end = sample.loop_end; + patch->base_note = sample.base_note; + patch->high_note = sample.high_note; + patch->low_note = sample.low_note; + patch->base_freq = sample.base_freq; + patch->detuning = sample.detune; + patch->panning = (sample.panning - 7) * 16; + + memcpy (patch->env_rate, sample.envelope_rate, 6); + memcpy (patch->env_offset, sample.envelope_offset, 6); + + patch->tremolo_sweep = sample.tremolo_sweep; + patch->tremolo_rate = sample.tremolo_rate; + patch->tremolo_depth = sample.tremolo_depth; + + patch->vibrato_sweep = sample.vibrato_sweep; + patch->vibrato_rate = sample.vibrato_rate; + patch->vibrato_depth = sample.vibrato_depth; + + patch->scale_frequency = sample.scale_frequency; + patch->scale_factor = sample.scale_factor; + + patch->volume = header.master_volume; + + if (fseek (fh, offset, 0) == -1) + { + fclose(fh); + return -1; + } + + if ((long)fread (patch->data, 1,sample.len,fh) != sample.len) + { +#ifdef GUSOUTDEBUG + printf ("Short file\n"); +#endif + return -1; + } + + SEQ_WRPATCH (patch, sizeof (*patch) + sample.len); + + offset = offset + sample.len; + + } + patchloaded[pgm]=1; + + fclose(fh); + free(patch); // Shouldn't this 'free' be within the 'for' loop ? + delete s; + freememory = device; + ioctl(seqfd, SNDCTL_SYNTH_MEMAVL, &freememory); +#endif + return 0; +} + + +void GUSOut::setPatchesToUse(int *patchesused) +{ +#ifdef HAVE_OSS_SUPPORT + int k; + for (k=0;k<256;k++) patchloaded[k]=0; + + int patchesordered[256]; //This holds the pgm used ordered by a method which + // put first the patches more oftenly used, and then the least + // In example, if a song only uses a piano and a splash cymbal, + // This is set to : 0,188,-1,-1,-1,-1 ... + patchesLoadingOrder(patchesused,patchesordered); + + // If above line doesn't work, perhaps you could try this ? : + // for (int j=0;j<256;j++) patchesordered[j]=patchesused[j]; +#ifdef GUSOUTDEBUG + printf("Patches used : \n"); + for (k=0;k<256;k++) + { + if (patchesused[k]!=-1) printf("%d,",patchesused[k]); + } + printf("\n Patches used, sorted :\n"); + for (k=0;k<256;k++) + { + if (patchesordered[k]!=-1) printf("%d,",patchesordered[k]); + } +#endif + + int i=0; + while (patchesordered[i]!=-1) + { +#ifdef GUSOUTDEBUG + printf("Load Patch : %d\n",patchesordered[i]); +#endif + loadPatch(patchesordered[i]); + i++; + } +#endif +} + +int compare_decreasing(const void *a,const void *b) +{ + struct instr_gm + { + int used; + int pgm; + }; + instr_gm *ai=(instr_gm *)a; + instr_gm *bi=(instr_gm *)b; + return ai->used<bi->used; +} + + +void GUSOut::patchesLoadingOrder(int *patchesused,int *patchesordered) +{ + struct instr_gm + { + int used; + int pgm; + }; + + instr_gm tempmelody[128]; + instr_gm tempdrums[128]; + int i,j; + for (i=0,j=128;i<128;i++,j++) + { + tempmelody[i].used=patchesused[i]; + tempmelody[i].pgm=i; + tempdrums[i].used=patchesused[j]; + tempdrums[i].pgm=j; + } + /* SORT */ // Decreasing order (first most used patch, then less used patch) + qsort(&tempmelody[0],128,sizeof(instr_gm),compare_decreasing); + qsort(&tempdrums[0],128,sizeof(instr_gm),compare_decreasing); + + /* Once they are sorted, the result is put on patchesordered in the following + * way : If tempmelody is : M0 M1 M2 M3 ... M127 and tempdrums is : + * D0 D1 D2 D3 ... D127, the result is : + * M0 D0 M1 M2 D1 M3 M4 D2 M5 M6 D3 ... + * P0 P1 P2 P3 P4 P5 P6 P7 P8 P9 P10 ... + */ + +#ifdef GUSOUTDEBUG + for (int k=0;k<128;k++) + { + printf("%d - %d\n",tempmelody[k].used,tempmelody[k].pgm); + } + for (int k=0;k<128;k++) + { + printf("%d : %d\n",tempdrums[k].used,tempdrums[k].pgm); + } +#endif + + i=0; + int totalmelody=0; + while ((i<128)&&(tempmelody[i].used!=0)) + { + totalmelody++; + i++; + } + i=0; + int totaldrums=0; + while ((i<128)&&(tempdrums[i].used!=0)) + { + totaldrums++; + i++; + } +#ifdef GUSOUTDEBUG + printf("Totalmelody : %d,totaldrums : %d\n",totalmelody,totaldrums); +#endif + int tgt=0; + + int tm=totalmelody; + int td=totaldrums; + int cm,cd; + cm=cd=0; + if ((tm!=0)&&(td!=0)) + { + patchesordered[0]=tempmelody[0].pgm; + patchesordered[1]=tempdrums[0].pgm; + tm--;td--; + cm++;cd++; + tgt+=2; + while ((tm>0)&&(td>0)) + { + if (((tgt-1)%3)==0) + { + patchesordered[tgt]=tempdrums[cd].pgm; + cd++; + td--; + } + else + { + patchesordered[tgt]=tempmelody[cm].pgm; + cm++; + tm--; + } + tgt++; + } + } + while (tm>0) + { + patchesordered[tgt]=tempmelody[cm].pgm; + tgt++; + cm++; + tm--; + } + while (td>0) + { + patchesordered[tgt]=tempdrums[cd].pgm; + tgt++; + cd++; + td--; + } + + // Now we put as not used (-1) the rest of the array + while (tgt<256) + { + patchesordered[tgt]=-1; + tgt++; + } +} + +//char *GUSOut::GUS_patches_directory="/mnt/dosc/gravis/patches"; +const char *GUSOut::GUS_patches_directory="/usr/share/ultrasnd"; + +int GUSOut::delete_GUS_patches_directory = 0; +/* No, this doesn't delete any file :-) it's just for internal use */ diff --git a/libkmid/gusout.h b/libkmid/gusout.h new file mode 100644 index 000000000..1fd8a3cbd --- /dev/null +++ b/libkmid/gusout.h @@ -0,0 +1,180 @@ +/* gusout.h - class gusOut which implements support for Gravis + Ultrasound cards through a /dev/sequencer device + This file is part of LibKMid 0.9.5 + Copyright (C) 1998,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _GUSOUT_H +#define _GUSOUT_H + +#include <libkmid/midiout.h> +#include <libkmid/voiceman.h> + +/** + * Gravis Ultrasound synthesizer output class . This class is used to send midi + * events to synthesizers on GUS cards. + * + * GUSOut inherits MidiOut and supports the same simple API. + * + * The recommended way to use this class is by using a DeviceManager + * object, and use the DeviceManager::setPatchesToUse() member which will + * call the setPatchesToUse() member in this class. + * + * @short Sends MIDI events to GUS synths + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class GUSOut : public MidiOut +{ + private: + class GUSOutPrivate; + GUSOutPrivate *di; + + int patchloaded[256]; + int nvoices; + + int use8bit; // Use 8 bit patches, instead of 16 bits to use less memory + VoiceManager *vm; + + int totalmemory; // Total memory in soundcard + int freememory; // Free memory + + + void patchesLoadingOrder(int *patchesused,int *patchesordered); + const char *patchName(int pgm); + + public: + /** + * Constructor. See MidiOut::MidiOut() for more information. + */ + GUSOut(int d=0,int total =12); + + /** + * Destructor. + */ + ~GUSOut(); + + /** + * See MidiOut::openDev() + */ + virtual void openDev (int sqfd); + + /** + * See MidiOut::closeDev() + */ + virtual void closeDev (void); + + /** + * See MidiOut::initDev() + */ + virtual void initDev (void); + + /** + * See MidiOut::noteOn() + */ + virtual void noteOn ( uchar chn, uchar note, uchar vel ); + + /** + * See MidiOut::noteOff() + */ + virtual void noteOff ( uchar chn, uchar note, uchar vel ); + + /** + * See MidiOut::keyPressure() + */ + virtual void keyPressure ( uchar chn, uchar note, uchar vel ); + + /** + * See MidiOut::chnPatchChange() + */ + virtual void chnPatchChange ( uchar chn, uchar patch ); + + /** + * See MidiOut::chnPressure() + */ + virtual void chnPressure ( uchar chn, uchar vel ); + + /** + * See MidiOut::chnPitchBender() + */ + virtual void chnPitchBender ( uchar chn, uchar lsb, uchar msb ); + + /** + * See MidiOut::chnController() + */ + virtual void chnController ( uchar chn, uchar ctl , uchar v ); + + /** + * It's an empty function, as GUS synths don't support System Exclusive + * messages + */ + virtual void sysex ( uchar *data,ulong size); + + /** + * See DeviceManager::setPatchesToUse() . All the information about this + * member is explained there because it's (for now) just a simple call to this + * function when the device used is a GUS device, and you're supposed to use + * a DeviceManager object instead of a GUSOut object except in rare ocassions. + * + * @see patch() + * @see loadPatch() + */ + void setPatchesToUse(int *patchesused); + + /** + * Loads a single patch on the synthesizer memory. + * @param pgm is the number of the GM patch when pgm is between 0 and 127. + * Values from 128 to 255 are used to represent the percussion instruments. + * @return 0 if OK and -1 if there was an error (patch not found, not enough + * memory, etc.) + * + * @see patch() + * @see setPatchesToUse() + */ + int loadPatch (int pgm); + + /** + * Returns p if the patch with number p has been correctly loaded. + * In the case it hasn't been loaded, it returns the number of another patch + * that is loaded and that should be used instead. + * + * @see loadPatch() + * @see setPatchesToUse() + */ + int patch(int p); + + private: + static const char *GUS_patches_directory; + static int delete_GUS_patches_directory; + + public: + /** + * Sets the directory where the GUS patches are stored, that is, where the + * acpiano.pat, ... files can be found. + * + * It will store a copy of the parameter, so you should delete the memory + * used by the parameter you passed. + */ + static void setGUSPatchesDirectory(const char *dir); + +}; + +#endif diff --git a/libkmid/gusvoices.h b/libkmid/gusvoices.h new file mode 100644 index 000000000..da364eb18 --- /dev/null +++ b/libkmid/gusvoices.h @@ -0,0 +1,286 @@ +/* gusvoices.h - struct with Gravis Ultrasound patches' names + This file is part of LibKMid 0.9.5 + Copyright (C) 1998,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +const char GUS_voice_names[256][9] = +{ + /* 0 */ "acpiano", + /* 1 */ "britepno", + /* 2 */ "synpiano", + /* 3 */ "honky", + /* 4 */ "epiano1", + /* 5 */ "epiano2", + /* 6 */ "hrpschrd", + /* 7 */ "clavinet", + /* 8 */ "celeste", + /* 9 */ "glocken", + /* 10 */ "musicbox", + /* 11 */ "vibes", + /* 12 */ "marimba", + /* 13 */ "xylophon", + /* 14 */ "tubebell", + /* 15 */ "santur", + /* 16 */ "homeorg", + /* 17 */ "percorg", + /* 18 */ "rockorg", + /* 19 */ "church", + /* 20 */ "reedorg", + /* 21 */ "accordn", + /* 22 */ "harmonca", + /* 23 */ "concrtna", + /* 24 */ "nyguitar", + /* 25 */ "acguitar", + /* 26 */ "jazzgtr", + /* 27 */ "cleangtr", + /* 28 */ "mutegtr", + /* 29 */ "odguitar", + /* 30 */ "distgtr", + /* 31 */ "gtrharm", + /* 32 */ "acbass", + /* 33 */ "fngrbass", + /* 34 */ "pickbass", + /* 35 */ "fretless", + /* 36 */ "slapbas1", + /* 37 */ "slapbas2", + /* 38 */ "synbass1", + /* 39 */ "synbass2", + /* 40 */ "violin", + /* 41 */ "viola", + /* 42 */ "cello", + /* 43 */ "contraba", +// /* 44 */ "marcato", + /* 44 */ "tremstr", + /* 45 */ "pizzcato", + /* 46 */ "harp", + /* 47 */ "timpani", + /* 48 */ "marcato", + /* 49 */ "slowstr", + /* 50 */ "synstr1", + /* 51 */ "synstr2", + /* 52 */ "choir", + /* 53 */ "doo", + /* 54 */ "voices", + /* 55 */ "orchhit", + /* 56 */ "trumpet", + /* 57 */ "trombone", + /* 58 */ "tuba", + /* 59 */ "mutetrum", + /* 60 */ "frenchrn", + /* 61 */ "hitbrass", + /* 62 */ "synbras1", + /* 63 */ "synbras2", + /* 64 */ "sprnosax", + /* 65 */ "altosax", + /* 66 */ "tenorsax", + /* 67 */ "barisax", + /* 68 */ "oboe", + /* 69 */ "englhorn", + /* 70 */ "bassoon", + /* 71 */ "clarinet", + /* 72 */ "piccolo", + /* 73 */ "flute", + /* 74 */ "recorder", + /* 75 */ "woodflut", + /* 76 */ "bottle", + /* 77 */ "shakazul", + /* 78 */ "whistle", + /* 79 */ "ocarina", + /* 80 */ "sqrwave", + /* 81 */ "sawwave", + /* 82 */ "calliope", + /* 83 */ "chiflead", +// /* 84 */ "voxlead", + /* 84 */ "charang", + /* 85 */ "voxlead", + /* 86 */ "lead5th", + /* 87 */ "basslead", + /* 88 */ "fantasia", + /* 89 */ "warmpad", + /* 90 */ "polysyn", + /* 91 */ "ghostie", + /* 92 */ "bowglass", + /* 93 */ "metalpad", + /* 94 */ "halopad", + /* 95 */ "sweeper", + /* 96 */ "aurora", + /* 97 */ "soundtrk", + /* 98 */ "crystal", + /* 99 */ "atmosphr", + /* 100 */ "freshair", + /* 101 */ "unicorn", +// /* 102 */ "sweeper", + /* 102 */ "echovox", + /* 103 */ "startrak", + /* 104 */ "sitar", + /* 105 */ "banjo", + /* 106 */ "shamisen", + /* 107 */ "koto", + /* 108 */ "kalimba", + /* 109 */ "bagpipes", + /* 110 */ "fiddle", + /* 111 */ "shannai", + /* 112 */ "carillon", + /* 113 */ "agogo", + /* 114 */ "steeldrm", + /* 115 */ "woodblk", + /* 116 */ "taiko", + /* 117 */ "toms", + /* 118 */ "syntom", + /* 119 */ "revcym", + /* 120 */ "fx-fret", + /* 121 */ "fx-blow", + /* 122 */ "seashore", + /* 123 */ "jungle", + /* 124 */ "telephon", + /* 125 */ "helicptr", + /* 126 */ "applause", + /* 127 */ "pistol", + + "", /* 128 = drum 0*/ + "", /* 129 = drum 1*/ + "", /* 130 = drum 2*/ + "", /* 131 = drum 3*/ + "", /* 132 = drum 4*/ + "", /* 133 = drum 5*/ + "", /* 134 = drum 6*/ + "", /* 135 = drum 7*/ + "", /* 136 = drum 8*/ + "", /* 137 = drum 9*/ + "", /* 138 = drum 10*/ + "", /* 139 = drum 11*/ + "", /* 140 = drum 12*/ + "", /* 141 = drum 13*/ + "", /* 142 = drum 14*/ + "", /* 143 = drum 15*/ + "", /* 144 = drum 16*/ + "", /* 145 = drum 17*/ + "", /* 146 = drum 18*/ + "", /* 147 = drum 19*/ + "", /* 148 = drum 20*/ + "", /* 149 = drum 21*/ + "", /* 150 = drum 22*/ + "", /* 151 = drum 23*/ + "", /* 152 = drum 24*/ + "", /* 153 = drum 25*/ + "", /* 154 = drum 26*/ + "highq", /* 155 = drum 27*/ + "slap", /* 156 = drum 28*/ + "scratch1", /* 157 = drum 29*/ + "scratch2", /* 158 = drum 30*/ + "sticks", /* 159 = drum 31*/ + "sqrclick", /* 160 = drum 32*/ + "metclick", /* 161 = drum 33*/ + "metbell", /* 162 = drum 34*/ + "kick1", /* 163 = drum 35*/ + "kick2", /* 164 = drum 36*/ + "stickrim", /* 165 = drum 37*/ + "snare1", /* 166 = drum 38*/ + "claps", /* 167 = drum 39*/ + "snare2", /* 168 = drum 40*/ + "tomlo2", /* 169 = drum 41*/ + "hihatcl", /* 170 = drum 42*/ + "tomlo1", /* 171 = drum 43*/ + "hihatpd", /* 172 = drum 44*/ + "tommid2", /* 173 = drum 45*/ + "hihatop", /* 174 = drum 46*/ + "tommid1", /* 175 = drum 47*/ + "tomhi2", /* 176 = drum 48*/ + "cymcrsh1", /* 177 = drum 49*/ + "tomhi1", /* 178 = drum 50*/ + "cymride1", /* 179 = drum 51*/ + "cymchina", /* 180 = drum 52*/ + "cymbell", /* 181 = drum 53*/ + "tamborin", /* 182 = drum 54*/ + "cymsplsh", /* 183 = drum 55*/ + "cowbell", /* 184 = drum 56*/ + "cymcrsh2", /* 185 = drum 57*/ + "vibslap", /* 186 = drum 58*/ + "cymride2", /* 187 = drum 59*/ + "bongohi", /* 188 = drum 60*/ + "bongolo", /* 189 = drum 61*/ + "congahi1", /* 190 = drum 62*/ + "congahi2", /* 191 = drum 63*/ + "congalo", /* 192 = drum 64*/ + "timbaleh", /* 193 = drum 65*/ + "timbalel", /* 194 = drum 66*/ + "agogohi", /* 195 = drum 67*/ + "agogolo", /* 196 = drum 68*/ + "cabasa", /* 197 = drum 69*/ + "maracas", /* 198 = drum 70*/ + "whistle1", /* 199 = drum 71*/ + "whistle2", /* 200 = drum 72*/ + "guiro1", /* 201 = drum 73*/ + "guiro2", /* 202 = drum 74*/ + "clave", /* 203 = drum 75*/ + "woodblk1", /* 204 = drum 76*/ + "woodblk2", /* 205 = drum 77*/ + "cuica1", /* 206 = drum 78*/ + "cuica2", /* 207 = drum 79*/ + "triangl1", /* 208 = drum 80*/ + "triangl2", /* 209 = drum 81*/ + "shaker", /* 210 = drum 82*/ + "jingles", /* 211 = drum 83*/ + "belltree", /* 212 = drum 84*/ + "castinet", /* 213 = drum 85*/ + "surdo1", /* 214 = drum 86*/ + "surdo2", /* 215 = drum 87*/ + "", /* 216 = drum 88*/ + "", /* 217 = drum 89*/ + "", /* 218 = drum 90*/ + "", /* 219 = drum 91*/ + "", /* 220 = drum 92*/ + "", /* 221 = drum 93*/ + "", /* 222 = drum 94*/ + "", /* 223 = drum 95*/ + "", /* 224 = drum 96*/ + "", /* 225 = drum 97*/ + "", /* 226 = drum 98*/ + "", /* 227 = drum 99*/ + "", /* 228 = drum 100*/ + "", /* 229 = drum 101*/ + "", /* 230 = drum 102*/ + "", /* 231 = drum 103*/ + "", /* 232 = drum 104*/ + "", /* 233 = drum 105*/ + "", /* 234 = drum 106*/ + "", /* 235 = drum 107*/ + "", /* 236 = drum 108*/ + "", /* 237 = drum 109*/ + "", /* 238 = drum 110*/ + "", /* 239 = drum 111*/ + "", /* 240 = drum 112*/ + "", /* 241 = drum 113*/ + "", /* 242 = drum 114*/ + "", /* 243 = drum 115*/ + "", /* 244 = drum 116*/ + "", /* 245 = drum 117*/ + "", /* 246 = drum 118*/ + "", /* 247 = drum 119*/ + "", /* 248 = drum 120*/ + "", /* 249 = drum 121*/ + "", /* 250 = drum 122*/ + "", /* 251 = drum 123*/ + "", /* 252 = drum 124*/ + "", /* 253 = drum 125*/ + "", /* 254 = drum 126*/ + "" /* 255 = drum 127*/ +}; diff --git a/libkmid/libkmid.cc b/libkmid/libkmid.cc new file mode 100644 index 000000000..59e96ad0f --- /dev/null +++ b/libkmid/libkmid.cc @@ -0,0 +1,263 @@ +/************************************************************************** + + libkmid.cc - class KMidSimpleAPI that makes it easy to use libkmid + and a C wrapper. + This file is part of LibKMid 0.9.5 + Copyright (C) 2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ + +#include "libkmid.h" +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/shm.h> + +#include "deviceman.h" +#include "player.h" +#include "midimapper.h" + +struct kMidData kMid; + +int KMidSimpleAPI::kMidInit(void) +{ + kMid.midi = new DeviceManager(); + if ( kMid.midi == 0L ) return 1; + kMid.midi->initManager(); + if (!kMid.midi->ok()) return 1; + + kMid.pctlsmID=shmget(getpid(),sizeof(PlayerController),0600 | IPC_CREAT); + if (kMid.pctlsmID==-1) return 1; + kMid.pctl=(PlayerController *)shmat(kMid.pctlsmID,NULL,0); + if (kMid.pctl==NULL) return 1; + + kMid.player=new MidiPlayer(kMid.midi,kMid.pctl); + if ( kMid.player == 0L ) + { + delete kMid.midi; + return 1; + } + + kMid.player->setParseSong(false); + + kMid.pctl->message=0; + kMid.pctl->gm=1; + kMid.pctl->error=0; + kMid.pctl->ratioTempo=1.0; + kMid.pctl->tempo=500000; + kMid.pctl->volumepercentage=100; + for (int i=0;i<16;i++) + { + kMid.pctl->forcepgm[i]=0; + kMid.pctl->pgm[i]=0; + } + + return 0; +} + +int KMidSimpleAPI::kMidLoad(const char *filename) +{ + if (kMidDevices()==0) return 0; + return kMid.player->loadSong(filename); +} + +int KMidSimpleAPI::kMidPlay(int loop) +{ + if (kMidDevices()==0) return 4; + if (!kMid.player->isSongLoaded()) return 1; + if (kMid.pctl->playing==1) return 2; + if (kMid.midi->checkInit()==-1) return 3; + kMid.pctl->message=0; + kMid.pctl->playing=0; + kMid.pctl->finished=0; + kMid.pctl->error=0; + kMid.pctl->SPEVplayed=0; + kMid.pctl->SPEVprocessed=0; + kMid.pctl->millisecsPlayed=0; + if ((kMid.pid=fork())==0) + { + if (loop) + { + while (1) + { + kMid.player->play(); + if (kMid.pctl->error) return 5; + kMid.pctl->message=0; + kMid.pctl->playing=0; + kMid.pctl->finished=0; + kMid.pctl->error=0; + kMid.pctl->SPEVplayed=0; + kMid.pctl->SPEVprocessed=0; + kMid.pctl->millisecsPlayed=0; + } + + } else { + kMid.player->play(); + if (kMid.pctl->error) return 5; + } + _exit(0); + } else return 4; + return 0; +} + +int KMidSimpleAPI::kMidStop(void) +{ + if (kMidDevices()==0) return 4; + if (kMid.pctl->playing==0) return 1; + if (kMid.pid!=0) + { + kill(kMid.pid,SIGTERM); + waitpid(kMid.pid, NULL, 0); + kMid.pid=0; + } else return 2; + + kMid.pctl->playing=0; + return 0; +} + +void KMidSimpleAPI::kMidDestruct(void) +{ + delete kMid.midi; + kMid.midi=0L; + delete kMid.player; + kMid.player=0L; + delete kMid.map; + shmdt((char *)kMid.pctl); + shmctl(kMid.pctlsmID, IPC_RMID, 0L); +} + +int KMidSimpleAPI::kMidIsPlaying(void) +{ + return kMid.pctl->playing; +} + +int KMidSimpleAPI::kMidDevices(void) +{ + return kMid.midi->midiPorts()+kMid.midi->synthDevices(); +} + +const char * KMidSimpleAPI::kMidName(int i) +{ + return kMid.midi->name(i); +} + +const char * KMidSimpleAPI::kMidType(int i) +{ + return kMid.midi->type(i); +} + +void KMidSimpleAPI::kMidSetDevice(int i) +{ + kMid.midi->setDefaultDevice(i); +} + +void KMidSimpleAPI::kMidSetMidiMapper(const char *mapfilename) +{ + if (kMidDevices()==0) return; + kMid.map=new MidiMapper(mapfilename); + if ((kMid.map->ok() == 0L)||(!kMid.map->ok())) return; + kMid.midi->setMidiMap(kMid.map); +} + +const char *KMidSimpleAPI::kMidVersion(void) +{ + return "0.9.5"; +} + +const char *KMidSimpleAPI::kMidCopyright(void) +{ + return "LibKMid 0.9.5 (C)1997-2000 Antonio Larrosa Jimenez <larrosa@kde.org>.Malaga(es)"; +} + +/* * * * * * + + Under this line (------) there's only a C wrapper for the KMidSimpleAPI class + +* * * * * */ + + +int kMidInit(void) +{ + return KMidSimpleAPI::kMidInit(); +} + +int kMidLoad(const char *filename) +{ + return KMidSimpleAPI::kMidLoad(filename); +} + +int kMidPlay(void) +{ + return KMidSimpleAPI::kMidPlay(); +} + +int kMidStop(void) +{ + return KMidSimpleAPI::kMidStop(); +} + +void kMidDestruct(void) +{ + KMidSimpleAPI::kMidDestruct(); +} + +int kMidIsPlaying(void) +{ + return KMidSimpleAPI::kMidIsPlaying(); +} + +int kMidDevices(void) +{ + return KMidSimpleAPI::kMidDevices(); +} + +const char *kMidName(int i) +{ + return KMidSimpleAPI::kMidName(i); +} + +const char *kMidType(int i) +{ + return KMidSimpleAPI::kMidType(i); +} + +void kMidSetDevice(int i) +{ + KMidSimpleAPI::kMidSetDevice(i); +} + +void kMidSetMidiMapper(const char *mapfilename) +{ + KMidSimpleAPI::kMidSetMidiMapper(mapfilename); +} + +const char *kMidVersion(void) +{ + return KMidSimpleAPI::kMidVersion(); +} + +const char *kMidCopyright(void) +{ + return KMidSimpleAPI::kMidCopyright(); +} + diff --git a/libkmid/libkmid.h b/libkmid/libkmid.h new file mode 100644 index 000000000..45f41bd79 --- /dev/null +++ b/libkmid/libkmid.h @@ -0,0 +1,226 @@ +/* libkmid.h - class KMidSimpleAPI that makes it easy to use libkmid + and a C wrapper. + This file is part of LibKMid 0.9.5 + Copyright (C) 2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _LIBKMID_H +#define _LIBKMID_H + +#ifdef __cplusplus + +#include <kdelibs_export.h> + +/** + * Simple API covering most of the uses of libkmid. + * + * You can use the members of this class in pure C applications, just by using + * the same name as the corresponding function member. + * + * Suppose you're developing a game and you want to play some background music + * while the user is playing. You only have to call : + * + * @li kMidInit(); + * @li kMidLoad("RideOfTheValkyries.mid"); + * @li kMidPlay(); + * + * When the user decides to quit the game, use + * + * @li kMidStop(); + * @li kMidDestruct(); + * + * to stop the music and release the memory allocated by libkmid. + * + * @short A very simple API around the rest of libkmid. + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class KMID_EXPORT KMidSimpleAPI +{ + private: + class KMidSimpleAPIPrivate; + KMidSimpleAPIPrivate *d; + + public: + + /** + * Initializes libkmid. Creates the DeviceManager object, and initializes + * some variables that will be used later. + * + * @return 0 if OK, and a positive number when there's any error (for + * example, because the /dev/sequencer device couldn't be opened, be it + * because it was already opened by another application, or because the + * sound card wasn't configured) + */ + static int kMidInit(void); + + /** + * Loads a song that will be played with the next call to kMidPlay(). + */ + static int kMidLoad(const char *filename); + + /** + * Plays the song currently loaded with kMidLoad(). + * kMidPlay forks in order to play the song in a different process, it + * exits inmediately, so that the application can follow the normal + * execution flow while the sone is played. + * + * If loop is 0 the song is played once and then the child process + * finishes. If loop is 1, the song is played repeatedly until + * kMidStop() is called. You can call kMidStop() anytime you want + * (also if loop is 0) to stop the song and kill the child process. + * + * @see kMidStop + * @see kMidIsPlaying + */ + static int kMidPlay(int loop=0); + + /** + * Stops playing a song inmediatly. It doesn't return until the child + * process that is playing the song is terminated. + * + * @see kMidPlay + */ + static int kMidStop(void); + + /** + * Releases the memory allocated by libkmid. To continue playing, you must + * first make a(nother) call to kMidInit(). + */ + static void kMidDestruct(void); + + /** + * Returns 1 if the library is playing a song, and 0 if it's not. + * @see kMidPlay + */ + static int kMidIsPlaying(void); + + /** + * Returns the number of MIDI devices ( MIDI ports + synthesizers ) + * @see DeviceManager::midiPorts + * @see DeviceManager::synthDevices + * @see kMidName + * @see kMidType + */ + static int kMidDevices(void); + + /** + * Returns the name of the i-th device . In case libkmid wasn't yet + * initialized ( see kMidInit() ), the return value is NULL, and in + * case the parameter has a value out of the valid range + * ( see kMidDevices() ) it returns an empty string. + * + * @see kMidDevices + * @see kMidType + */ + static const char *kMidName(int i); + + /** + * Returns the type of the i-th device . In case libkmid wasn't yet + * initialized ( see kMidInit() ), the return value is NULL, and in + * case the parameter has a value out of the valid range + * ( see kMidDevices() ) it returns an empty string. + * + * @see kMidDevices + * @see kMidName + */ + static const char *kMidType(int i); + + /** + * Sets the MIDI device to use when playing a song. + * @see kMidDevices + * @see kMidName + * @see DeviceManager + */ + static void kMidSetDevice(int i); + + /** + * Sets the Midi Mapper to use. Most of the users won't need a midi mapper, + * but there're still non-General Midi synthesizers out there, and people + * with one of those will get much better sound quality by using a MIDI + * mapper. + * + * Please have a look at KMid's documentation for more information + * about MIDI mappers and how to write a MIDI mapper for your keyboard. + */ + static void kMidSetMidiMapper(const char *mapfilename); + + /** + * Returns the version number of libkmid, i.e. "0.9.5" or "1.0 Beta" + */ + static const char *kMidVersion(void); + + /** + * Returns the copyright notice that applications using libkmid should print + * to the user in an about box or somewhere visible. + * I.e. + * + * "LibKMid 0.9.5 (C) 1997-2000 Antonio Larrosa Jimenez <larrosa@kde.org>. Spain" + */ + static const char *kMidCopyright(void); + +}; + + + +extern "C" { + +#else +#define KMID_EXPORT +#endif + + +KMID_EXPORT int kMidInit(void); +KMID_EXPORT int kMidLoad(const char *filename); +KMID_EXPORT int kMidPlay(void); +KMID_EXPORT int kMidStop(void); +KMID_EXPORT void kMidDestruct(void); +KMID_EXPORT int kMidIsPlaying(void); +KMID_EXPORT int kMidDevices(void); +KMID_EXPORT const char * kMidName(int i); +KMID_EXPORT const char * kMidType(int i); +KMID_EXPORT void kMidSetDevice(int i); +KMID_EXPORT void kMidSetMidiMapper(const char *mapfilename); +KMID_EXPORT const char * kMidVersion(void); +KMID_EXPORT const char * kMidCopyright(void); + + + +#ifdef __cplusplus + +} + +/** + * @internal + */ +extern struct kMidData +{ + class DeviceManager *midi; + class MidiPlayer *player; + class MidiMapper *map; + struct PlayerController *pctl; + int pctlsmID; + int pid; +} kMid; +#endif + + +#endif diff --git a/libkmid/midfile.cc b/libkmid/midfile.cc new file mode 100644 index 000000000..baf0a2b37 --- /dev/null +++ b/libkmid/midfile.cc @@ -0,0 +1,460 @@ +/************************************************************************** + + midfile.cc - function which reads a midi file,and creates the track classes + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "midfile.h" +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include "sndcard.h" +#include "midispec.h" +#include "mt32togm.h" +#include "sys/stat.h" +#include <config.h> + +#include <kprocess.h> +#include <qfile.h> + +int fsearch(FILE *fh,const char *text,long *ptr); + +/* This function gives the metronome tempo, from a tempo data as found in + a midi file */ +double tempoToMetronomeTempo(ulong x) +{ + return 60/((double)x/1000000); +} + +double metronomeTempoToTempo(ulong x) +{ + return ((double)60*x)/1000000; +} + +int uncompressFile(const char *gzname, char *tmpname) + // Returns 0 if OK, 1 if error (tmpname not set) +{ + QString cmd("gzip -dc " + KProcess::quote(gzname)); + FILE *infile = popen( QFile::encodeName(cmd).data(), "r"); + if (infile==NULL) { + fprintf(stderr,"ERROR : popen failed : %s\n",QFile::encodeName(cmd).data()); + return 1; + } + strcpy(tmpname, "/tmp/KMid.XXXXXXXXXX"); + int fd = mkstemp(tmpname); + if (fd == -1) + { + pclose(infile); + return 1; + } + FILE *outfile= fdopen(fd,"wb"); + if (outfile==NULL) + { + pclose(infile); + return 1; + } + int n=getc(infile); + if (n==EOF) + { + pclose(infile); + fclose(outfile); + unlink(tmpname); + return 1; + } + fputc(n,outfile); + int buf[BUFSIZ]; + n = fread(buf, 1, BUFSIZ, infile); + while (n>0) + { + fwrite(buf, 1, n, outfile); + n = fread(buf, 1, BUFSIZ, infile); + } + + pclose(infile); + + //if (pclose(infile) != 0) fprintf(stderr,"Error : pclose failed\n"); + // Is it right for pclose to always fail ? + + fclose(outfile); + return 0; +} + +MidiTrack **readMidiFile( const char *name, MidiFileInfo *info, int &ok) +{ + ok=1; + MidiTrack **tracks; + + struct stat buf; + if (stat(name,&buf) || !S_ISREG(buf.st_mode)) + { + fprintf(stderr,"ERROR: %s is not a regular file\n",name); + ok=-6; + return NULL; + } + + FILE *fh=fopen(name,"rb"); + if (fh==NULL) + { + fprintf(stderr,"ERROR: Can't open file %s\n",name); + ok=-1; + return NULL; + } + char text[4]; + text[0] = 0; + fread(text,1,4,fh); + if ((strncmp(text,"MThd",4)!=0)&&(strcmp(&name[strlen(name)-3],".gz")==0)) + { + fclose(fh); + char tempname[200]; + fprintf(stderr,"Trying to open zipped midi file...\n"); + if (uncompressFile(name,tempname)!=0) + { + fprintf(stderr,"ERROR: %s is not a (zipped) midi file\n",name); + ok=-2; + return NULL; + } + fh=fopen(tempname,"rb"); + fread(text,1,4,fh); + unlink(tempname); + } + + if (strncmp(text,"MThd",4)!=0) + { + fseek(fh,0,SEEK_SET); + long pos; + if (fsearch(fh,"MThd",&pos)==0) + { + fclose(fh); + fprintf(stderr,"ERROR: %s is not a midi file.\n",name); + ok=-2; + return NULL; + } + fseek(fh,pos,SEEK_SET); + fread(text,1,4,fh); + } + long header_size=readLong(fh); + info->format=readShort(fh); + info->ntracks=readShort(fh); + info->ticksPerCuarterNote=readShort(fh); + if (info->ticksPerCuarterNote<0) + { + fprintf(stderr,"ERROR: Ticks per cuarter note is negative !\n"); + fprintf(stderr,"Please report this error to : larrosa@kde.org\n"); + fclose(fh); + ok=-3; + return NULL; + } + if (header_size>6) fseek(fh,header_size-6,SEEK_CUR); + tracks=new MidiTrack*[info->ntracks]; + if (tracks==NULL) + { + fprintf(stderr,"ERROR: Not enough memory\n"); + fclose(fh); + ok=-4; + return NULL; + } + int i=0; + while (i<info->ntracks) + { + fread(text,1,4,fh); + if (strncmp(text,"MTrk",4)!=0) + { + fprintf(stderr,"ERROR: Not a well built midi file\n"); + fprintf(stderr,"%s",text); + fclose(fh); + ok=-5; + return NULL; + } + tracks[i]=new MidiTrack(fh,info->ticksPerCuarterNote,i); + if (tracks[i]==NULL) + { + fprintf(stderr,"ERROR: Not enough memory"); + fclose(fh); + ok=-4; + return NULL; + } + i++; + } + + fclose(fh); + + return tracks; + +} + +void parseInfoData(MidiFileInfo *info,MidiTrack **tracks,float ratioTempo) +{ + + info->ticksTotal=0; + info->millisecsTotal=0.0; + info->ticksPlayed=0; + int i; + for (i=0;i<256;i++) + { + info->patchesUsed[i]=0; + } + + int parsing=1; + int trk,minTrk; + ulong tempo=(ulong)(500000 * ratioTempo); + +#ifdef MIDFILEDEBUG + printf("Parsing 1 ...\n"); +#endif + + int pgminchannel[16]; + for (i=0;i<16;i++) + { + pgminchannel[i]=0; + } + + int j; + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + tracks[i]->changeTempo(tempo); + } + double prevms=0; + double minTime=0; + double maxTime; + MidiEvent *ev=new MidiEvent; + while (parsing) + { + prevms=minTime; + trk=0; + minTrk=0; + maxTime=minTime + 2 * 60000L; + minTime=maxTime; + while (trk<info->ntracks) + { + if (tracks[trk]->absMsOfNextEvent()<minTime) + { + minTrk=trk; + minTime=tracks[minTrk]->absMsOfNextEvent(); + } + trk++; + } + if ((minTime==maxTime)) + { + parsing=0; +#ifdef MIDFILEDEBUG + printf("END of parsing\n"); +#endif + } + else + { + trk=0; + while (trk<info->ntracks) + { + tracks[trk]->currentMs(minTime); + trk++; + } + } + trk=minTrk; + tracks[trk]->readEvent(ev); + + switch (ev->command) + { + case (MIDI_NOTEON) : + if (ev->chn!=PERCUSSION_CHANNEL) + info->patchesUsed[pgminchannel[ev->chn]]++; + else + info->patchesUsed[ev->note+128]++; + break; + case (MIDI_PGM_CHANGE) : + pgminchannel[ev->chn]=(ev->patch); + break; + case (MIDI_SYSTEM_PREFIX) : + if (((ev->command|ev->chn)==META_EVENT)&&(ev->d1==ME_SET_TEMPO)) + { + tempo=(ulong)(((ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2])) * ratioTempo); + for (j=0;j<info->ntracks;j++) + { + tracks[j]->changeTempo(tempo); + } + } + break; + } + } + + delete ev; + info->millisecsTotal=prevms; + + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + } + +#ifdef MIDFILEDEBUG + printf("info.ticksTotal = %ld \n",info->ticksTotal); + printf("info.ticksPlayed= %ld \n",info->ticksPlayed); + printf("info.millisecsTotal = %g \n",info->millisecsTotal); + printf("info.TicksPerCN = %d \n",info->ticksPerCuarterNote); +#endif + +} + + +void parsePatchesUsed(MidiTrack **tracks,MidiFileInfo *info,int gm) +{ + int i; + for (i=0;i<256;i++) + { + info->patchesUsed[i]=0; + } + int parsing=1; + int trk,minTrk; + ulong tempo=500000; + +#ifdef MIDFILEDEBUG + printf("Parsing for patches ...\n"); +#endif + + int j; + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + } + double prevms=0; + double minTime=0; + double maxTime; + ulong tmp; + MidiEvent *ev=new MidiEvent; + int pgminchannel[16]; + for (i=0;i<16;i++) + { + pgminchannel[i]=0; + } + + while (parsing) + { + prevms=minTime; + trk=0; + minTrk=0; + maxTime=minTime + 2 * 60000L; + minTime=maxTime; + while (trk<info->ntracks) + { + if (tracks[trk]->absMsOfNextEvent()<minTime) + { + minTrk=trk; + minTime=tracks[minTrk]->absMsOfNextEvent(); + } + trk++; + } + if ((minTime==maxTime)) + { + parsing=0; +#ifdef MIDFILEDEBUG + printf("END of parsing for patches\n"); +#endif + } + else + { + trk=0; + while (trk<info->ntracks) + { + tracks[trk]->currentMs(minTime); + trk++; + } + } + trk=minTrk; + tracks[trk]->readEvent(ev); + switch (ev->command) + { + case (MIDI_NOTEON) : + if (ev->chn!=PERCUSSION_CHANNEL) + info->patchesUsed[pgminchannel[ev->chn]]++; + else + info->patchesUsed[ev->note+128]++; + break; + case (MIDI_PGM_CHANGE) : + pgminchannel[ev->chn]=(gm==1)?(ev->patch):(MT32toGM[ev->patch]); + break; + case (MIDI_SYSTEM_PREFIX) : + if (((ev->command|ev->chn)==META_EVENT)&&(ev->d1==ME_SET_TEMPO)) + { + if (tempoToMetronomeTempo(tmp=((ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2])))>=8) + { + tempo=tmp; + // printf("setTempo %ld\n",tempo); + for (j=0;j<info->ntracks;j++) + { + tracks[j]->changeTempo(tempo); + } + } + } + break; + } + } + + delete ev; + + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + } + +} + +int fsearch(FILE *fh,const char *text,long *ptr) + // Search for "text" through the fh file and then returns : + // text MUST BE smaller than 256 characters + // 0 if not was found + // 1 if it was found and in ptr (if !=NULL) the position where text begins. +{ + if ((text==NULL)||(text[0]==0)) return 0; + char buf[1024]; + char tmp[256]; + long pos; + int l=strlen(text); + int i,k,r; + while (!feof(fh)) + { + pos=ftell(fh); + k=fread(buf,1,1024,fh); + i=0; + while (i<k) + { + if (buf[i]==text[0]) + { + if (k-i>=l) + r=strncmp(text,&buf[i],l); + else + { + fseek(fh,pos+i,SEEK_SET); + if (fread(tmp,1,l,fh)<(uint)l) return 0; + fseek(fh,pos+k,SEEK_SET); + r=strncmp(text,tmp,l); + } + if (r==0) + { + if (ptr!=NULL) *ptr=pos+i; + return 1; + } + } + i++; + } + } + return 0; +} diff --git a/libkmid/midfile.h b/libkmid/midfile.h new file mode 100644 index 000000000..843e4fc44 --- /dev/null +++ b/libkmid/midfile.h @@ -0,0 +1,99 @@ +/* midfile.h - function which reads a midi file,and creates the track classes + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + + ***************************************************************************/ +#ifndef _MIDFILE_H +#define _MIDFILE_H + +#include <libkmid/dattypes.h> +#include <libkmid/track.h> +#include <stdio.h> +#include <kdelibs_export.h> + +/** + * Contains all the information about a MIDI file. + * + * @short All the information about a MIDI file. + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +struct MidiFileInfo +{ + /** + * Format of MIDI file. + */ + int format; + + /** + * Number of tracks. + */ + int ntracks; + + /** + * Ticks per cuarter note. + */ + int ticksPerCuarterNote; + + /** + * Total number of MIDI ticks + */ + ulong ticksTotal; + + /** + * Total number of milliseconds + */ + double millisecsTotal; + + ulong ticksPlayed; + + /** + * Patches used in the MIDI file. + * + * In each position of the array it stores the number of times the + * corresponding patch is used. So, if a MIDI file plays 782 notes + * with a piano, patchesUsed[0] will store 782. In the same way, + * if it doesn't use the Music Box patch, patchesUsed[10] will be 0. + * + */ + int patchesUsed[256]; + +}; + +double KMID_EXPORT tempoToMetronomeTempo(ulong x); +double metronomeTempoToTempo(ulong x); + +/** + * Reads a midi file. + * + * @param name the filename of the midi file to load. + * @param info a pointer to the MidiFileInfo struct that will be + * filled with the information of the loaded file. + * @param ok return status. + * @return an array of MidiTrack objects with the contents of the file. + */ +MidiTrack **readMidiFile( const char *name, MidiFileInfo *info, int &ok); + +void parseInfoData( MidiFileInfo *info, MidiTrack **tracks, float ratioTempo); + +void parsePatchesUsed( MidiTrack **tracks, MidiFileInfo *info, int gm); + +#endif diff --git a/libkmid/midimapper.cc b/libkmid/midimapper.cc new file mode 100644 index 000000000..0b8022b8e --- /dev/null +++ b/libkmid/midimapper.cc @@ -0,0 +1,456 @@ +/************************************************************************** + + midimapper.cc - The midi mapper object + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "midimapper.h" +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +MidiMapper::MidiMapper(const char *name) +{ + _ok=1; + keymaps=NULL; + _filename=NULL; + mapPitchBender=0; + mapExpressionToVolumeEvents=0; + if ((name==NULL)||(name[0]==0)) + { + deallocateMaps(); + int i; + for (i=0;i<16;i++) + { + channelmap[i]=i; + channelPatchForced[i]=-1; + } + for (i=0;i<128;i++) patchmap[i]=i; + } + else + loadFile(name); +} + +MidiMapper::~MidiMapper() +{ + if (_filename) free(_filename); + deallocateMaps(); +} + +void MidiMapper::deallocateMaps(void) +{ + int i; + for (i=0;i<16;i++) channelKeymap[i]=NULL; + for (i=0;i<128;i++) patchKeymap[i]=NULL; + Keymap *km; + while (keymaps!=NULL) + { + km=keymaps->next; + delete keymaps; + keymaps=km; + } +} + +void MidiMapper::getValue(char *s,char *v) +{ + char *c=s; + while ((*c!=0)&&(*c!='=')) c++; + if (*c==0) v[0]=0; + else + { + c++; + while (*c!=0) + { + *v=*c; + c++;v++; + } + *v=0; + } +} + +void MidiMapper::removeSpaces(char *s) +{ + char *a=s; + while ((*a!=0)&&(*a==' ')) a++; + if (*a==0) {*s=0;return;}; + while (*a!=0) + { + while ((*a!=0)&&(*a!=' ')&&(*a!=10)&&(*a!=13)) + { + *s=*a; + s++; + a++; + } + while ((*a!=0)&&((*a==' ')||(*a==10)||(*a==13))) a++; + *s=' ';s++; + if (*a==0) {*s=0;return;}; + } + *s=0; + +} + +int MidiMapper::countWords(char *s) +{ + int c=0; + while (*s!=0) + { + if (*s==' ') c++; + s++; + } + return c; +} + +void MidiMapper::getWord(char *t,char *s,int w) +{ + int i=0; + *t=0; + while ((*s!=0)&&(i<w)) + { + if (*s==' ') i++; + s++; + } + while ((*s!=0)&&(*s!=' ')&&(*s!=10)&&(*s!=13)) + { + *t=*s; + t++;s++; + } + *t=0; +} + + +void MidiMapper::loadFile(const char *name) +{ + _ok=1; + FILE *fh = fopen(name,"rt"); + if ( fh == NULL ) { _ok = -1; return; }; + char s[101]; + s[0] = 0; + if ( _filename != NULL ) free(_filename); + _filename = strdup(name); +#ifdef MIDIMAPPERDEBUG + printf("Loading mapper ...\n"); +#endif + while (!feof(fh)) + { + s[0]=0; + while ((!feof(fh))&&((s[0]==0)||(s[0]=='#'))) fgets(s,100,fh); + if (strncmp(s,"DEFINE",6)==0) + { + if (strncmp(&s[7],"PATCHMAP",8)==0) readPatchmap(fh); + else + if (strncmp(&s[7],"KEYMAP",6)==0) readKeymap(fh,s); + else + if (strncmp(&s[7],"CHANNELMAP",10)==0) readChannelmap(fh); + else + { + printf("ERROR: Unknown DEFINE line in map file\n"); + _ok=0; + } + if (_ok==0) + { + printf("The midi map file will be ignored\n"); + fclose(fh); + return; + } + } + else if (strncmp(s,"OPTIONS",7)==0) readOptions(fh); + } + fclose(fh); +} + +MidiMapper::Keymap *MidiMapper::createKeymap(char *name,uchar use_same_note,uchar note) +{ + Keymap *km=new Keymap; + strncpy(km->name, name, KM_NAME_SIZE); + km->name[KM_NAME_SIZE - 1] = 0; + + int i; + if (use_same_note==1) + { + for (i=0;i<128;i++) + km->key[i]=note; + } + else + { + for (i=0;i<128;i++) + km->key[i]=i; + } + addKeymap(km); + return km; +} + +void MidiMapper::addKeymap(Keymap *newkm) +{ + Keymap *km=keymaps; + if (keymaps==NULL) + { + keymaps=newkm; + newkm->next=NULL; + return; + } + while (km->next!=NULL) km=km->next; + km->next=newkm; + newkm->next=NULL; + return; +} + +MidiMapper::Keymap *MidiMapper::keymap(char *n) +{ + Keymap *km=keymaps; + while ((km!=NULL)&&(strcmp(km->name,n)!=0)) km=km->next; + return km; +} + +void MidiMapper::readOptions(FILE *fh) +{ +#ifdef MIDIMAPPERDEBUG + printf("Loading Options ... \n"); +#endif + char s[101]; + char v[101]; + char t[101]; + int fin=0; + mapPitchBender=0; + while (!fin) + { + s[0]=0; + while ((s[0]==0)||(s[0]=='#')) fgets(s,100,fh); + if (strncmp(s,"PitchBenderRatio",16)==0) + { + getValue(s,v); + removeSpaces(v); + getWord(t,v,0); + mapPitchBender=1; + pitchBenderRatio=atoi(t); + } + else if (strncmp(s,"MapExpressionToVolumeEvents",27)==0) mapExpressionToVolumeEvents=1; + else if (strncmp(s,"END",3)==0) + { + fin=1; + } + else + { + printf("ERROR: Invalid option in OPTIONS section of map file : (%s)\n",s); + _ok=0; + return; + } + } +} + +void MidiMapper::readPatchmap(FILE *fh) +{ + char s[101]; + char v[101]; + char t[101]; + char name[256]; /* Longer than t and 'AllKeysTo' */ + int i=0; + int j,w; +#ifdef MIDIMAPPERDEBUG + printf("Loading Patch map ... \n"); +#endif + while (i<128) + { + s[0]=0; + while ((s[0]==0)||(s[0]=='#')) fgets(s,100,fh); + getValue(s,v); + removeSpaces(v); + w=countWords(v); + j=0; + patchKeymap[i]=NULL; + patchmap[i]=i; + while (j<w) + { + getWord(t,v,j); + if (strcmp(t,"AllKeysTo")==0) + { + j++; + if (j>=w) + { + printf("ERROR: Invalid option in PATCHMAP section of map file\n"); + _ok=0; + return; + } + getWord(t,v,j); + sprintf(name,"AllKeysTo%s",t); + patchKeymap[i]=createKeymap(name,1,atoi(t)); + } + else + { + patchmap[i]=atoi(t); + } + j++; + } + i++; + } + s[0]=0; + while ((s[0]==0)||(s[0]=='#')||(s[0]==10)||(s[0]==13)) fgets(s,100,fh); + if (strncmp(s,"END",3)!=0) + { + printf("ERROR: End of section not found in map file\n"); + _ok=0; + return; + } +} + +void MidiMapper::readKeymap(FILE *fh,char *first_line) +{ + char s[101]; + char v[101]; +#ifdef MIDIMAPPERDEBUG + printf("Loading Key map ... %s",first_line); +#endif + removeSpaces(first_line); + getWord(v,first_line,2); + Keymap *km=new Keymap; + strncpy(km->name, v, KM_NAME_SIZE); + km->name[KM_NAME_SIZE - 1] = 0; + + int i=0; + while (i<128) + { + s[0]=0; + while ((s[0]==0)||(s[0]=='#')) fgets(s,100,fh); + getValue(s,v); + removeSpaces(v); + km->key[i]=atoi(v); + i++; + } + s[0]=0; + while ((s[0]==0)||(s[0]=='#')||(s[0]==10)||(s[0]==13)) fgets(s,100,fh); + if (strncmp(s,"END",3)!=0) + { + printf("ERROR: End of section not found in map file\n"); + _ok=0; + return; + } + addKeymap(km); +} + +void MidiMapper::readChannelmap(FILE *fh) +{ + char s[101]; + char v[101]; + char t[101]; + int i=0; + int w,j; +#ifdef MIDIMAPPERDEBUG + printf("Loading Channel map ... \n"); +#endif + while (i<16) + { + s[0]=0; + while ((s[0]==0)||(s[0]=='#')) fgets(s,100,fh); + getValue(s,v); + removeSpaces(v); + w=countWords(v); + j=0; + channelKeymap[i]=NULL; + channelPatchForced[i]=-1; + channelmap[i]=i; + while (j<w) + { + getWord(t,v,j); + if (strcmp(t,"Keymap")==0) + { + j++; + if (j>=w) + { + printf("ERROR: Invalid option in CHANNELMAP section of map file\n"); + _ok=0; + return; + } + getWord(t,v,j); + channelKeymap[i]=keymap(t); + } + else if (strcmp(t,"ForcePatch")==0) + { + j++; + if (j>=w) + { + printf("ERROR: Invalid option in CHANNELMAP section of map file\n"); + _ok=0; + return; + } + getWord(t,v,j); + channelPatchForced[i]=atoi(t); + } + else + { + channelmap[i]=atoi(t); + } + j++; + } + i++; + } + s[0]=0; + while ((s[0]==0)||(s[0]=='#')||(s[0]==10)||(s[0]==13)) fgets(s,100,fh); + if (strncmp(s,"END",3)!=0) + { + printf("END of section not found in map file\n"); + _ok=0; + return; + } + +} + +const char *MidiMapper::filename(void) +{ + return (_filename)? _filename : ""; +} + +uchar MidiMapper::key(uchar chn,uchar pgm, uchar note) +{ + uchar notemapped=note; + if (patchKeymap[pgm]!=NULL) notemapped=patchKeymap[pgm]->key[note]; + if (channelKeymap[chn]!=NULL) notemapped=channelKeymap[chn]->key[note]; + return notemapped; +} + +uchar MidiMapper::patch(uchar chn,uchar pgm) +{ + return (channelPatchForced[chn] == -1) ? + patchmap[pgm] : (uchar)channelPatchForced[chn] ; +} + +void MidiMapper::pitchBender(uchar ,uchar &lsb,uchar &msb) +{ + if (mapPitchBender) + { + short pbs=((short)msb<<7) | (lsb & 0x7F); + pbs=pbs-0x2000; + short pbs2=(((long)pbs*pitchBenderRatio)/4096); +#ifdef MIDIMAPPERDEBUG + printf("Pitch Bender (%d): %d -> %d \n",chn,pbs,pbs2); +#endif + pbs2=pbs2+0x2000; + lsb=pbs2 & 0x7F; + msb=(pbs2 >> 7)&0x7F; + } +} + +void MidiMapper::controller(uchar ,uchar &ctl, uchar &) +{ + if ((mapExpressionToVolumeEvents)&&(ctl==11)) ctl=7; +} diff --git a/libkmid/midimapper.h b/libkmid/midimapper.h new file mode 100644 index 000000000..702c104ca --- /dev/null +++ b/libkmid/midimapper.h @@ -0,0 +1,210 @@ +/* midimapper.h - The midi mapper object + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _MIDIMAPPER_H +#define _MIDIMAPPER_H + +#include <stdio.h> +#include <libkmid/dattypes.h> +#include <kdelibs_export.h> + +#define KM_NAME_SIZE 30 + +/** + * A Midi Mapper class which defines the way MIDI events are translated + * (or "mapped") to different ones. This way, when two MIDI devices "talk" + * in a somehow different way, they can still communicate. + * + * When the user has an external keyboard that is not compatible with the + * General Midi standard, he can use a MIDI mapper file to play files + * as if the synthesizer was GM compatible. + * + * Please see the KMid documentation + * ( http://www.arrakis.es/~rlarrosa/kmid.html ) for information on the + * format of a MIDI mapper definition file, and how they work. + * + * I created this class because I had one of those non-GM keyboards, + * so it can do everything I needed it to do for my keyboard to work + * exactly as a GM synth, and a few more things. Currently, it's the most + * featured MIDI mapper available. + * + * The usage of this class is quite simple, just create an object with + * a correct filename in the constructor and then use this object as + * parameter for DeviceManager::setMidiMap(). + * + * @short Midi Mapper + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class KMID_EXPORT MidiMapper +{ + private: + class MidiMapperPrivate; + MidiMapperPrivate *d; + + /** + * @internal + * Internal definition for Keymaps + */ + struct Keymap + { + char name[KM_NAME_SIZE]; + uchar key[128]; + struct Keymap *next; + }; + + int _ok; + + uchar channelmap[16]; + /** + * @internal + * It's a pointer to the Keymap to use for a channel + * This variable is used to get faster a given Keymap + * The index is the real channel (after mapping it) + */ + Keymap *channelKeymap[16]; + + /** + * @internal + * It's -1 if the channel doesn't have a forced patch, + * else indicates the patch to force in the channel. + */ + int channelPatchForced[16]; + + uchar patchmap[128]; + + /** + * @internal + * Same as channelKeymap + */ + Keymap *patchKeymap[128]; + + /** + * @internal + * Real linked list of keymaps used around the class. + */ + Keymap *keymaps; + + /** + * @internal + * Stores the name of the file from which the map was loaded + */ + char *_filename; + + /** + * @internal + * Simulate expression events with volume events + */ + int mapExpressionToVolumeEvents; + + /** + * @internal + * Map or not the Pitch Bender using pitchBenderRatio() + */ + int mapPitchBender; + + /** + * @internal + * Indicates the ratio between the standard and the synthesizer's pitch + * bender engine. The number sent to the synth is multiplied by this + * and dividied by 4096. Thus if PitchBenderRatio is 4096, the synth's + * pitch bender works as the standard one + */ + int pitchBenderRatio; + + void getValue(char *s,char *v); + void removeSpaces(char *s); + int countWords(char *s); + void getWord(char *t,char *s,int w); + // get from s the word in position w and store it in t + + void deallocateMaps(void); + Keymap *createKeymap(char *name,uchar use_same_note=0,uchar note=0); + void readPatchmap(FILE *fh); + void readKeymap(FILE *fh,char *first_line); + void readChannelmap(FILE *fh); + void readOptions(FILE *fh); + + void addKeymap(Keymap *newkm); + Keymap *keymap(char *n); + + public: + /** + * Constructor. Loads a MIDI Mapper definition from a file. + * @see filename() + */ + MidiMapper(const char *name); + + /** + * Destructor. + */ + ~MidiMapper(); + + /** + * Loads a MIDI Mapper definition file (you don't need to use this if you + * used a correct filename in constructor). + */ + void loadFile(const char *name); + + /** + * Returns the status of the object. + */ + int ok(void) { return _ok; } + + /** + * Returns the channel which chn should be mapped to. + */ + uchar channel(uchar chn) { return channelmap[chn];} + + /** + * Returns the patch which pgm used on channel chn should be mapped to. + */ + uchar patch(uchar chn,uchar pgm); + + /** + * Returns the key that key note playing a pgm patch on channel chn should + * be mapped to. + */ + uchar key(uchar chn,uchar pgm, uchar note); + + /** + * Returns the value which the pitch bender on channel chn should be + * mapped to. + */ + void pitchBender(uchar chn,uchar &lsb,uchar &msb); + + /** + * Returns the value which a given controller and its value should + * be mapped to when played on channel chn. + */ + void controller(uchar chn,uchar &ctl,uchar &v); + + /** + * Returns the path and name of the file which the object loaded the + * mapper from. + */ + const char *filename(void); + +}; + +#endif diff --git a/libkmid/midiout.cc b/libkmid/midiout.cc new file mode 100644 index 000000000..e4a03a405 --- /dev/null +++ b/libkmid/midiout.cc @@ -0,0 +1,301 @@ +/************************************************************************** + + midiout.cc - class midiOut which handles external midi devices + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "midiout.h" +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include "sndcard.h" +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <sys/param.h> +#include "midispec.h" +#include "alsaout.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/ioctl.h> + +SEQ_USE_EXTBUF(); + +MidiOut::MidiOut(int d) +{ + seqfd = -1; + devicetype=KMID_EXTERNAL_MIDI; + device= d; + volumepercentage=100; + map=new MidiMapper(NULL); + if (map==NULL) { printfdebug("ERROR : midiOut : Map is NULL\n"); return; }; + _ok=1; +} + +MidiOut::~MidiOut() +{ + delete map; + closeDev(); +} + +void MidiOut::openDev (int sqfd) +{ +#ifdef HAVE_OSS_SUPPORT + _ok=1; + seqfd=sqfd; + if (seqfd==-1) + { + printfdebug("ERROR: Could not open /dev/sequencer\n"); + _ok=0; + return; + } +#endif +} + +void MidiOut::closeDev (void) +{ + if (!ok()) return; +// if (deviceType()!=KMID_ALSA) allNotesOff(); + SEQ_STOP_TIMER(); + SEQ_DUMPBUF(); + seqfd=-1; +} + +void MidiOut::initDev (void) +{ +#ifdef HAVE_OSS_SUPPORT + int chn; + if (!ok()) return; + uchar gm_reset[5]={0x7e, 0x7f, 0x09, 0x01, 0xf7}; + sysex(gm_reset, sizeof(gm_reset)); + for (chn=0;chn<16;chn++) + { + chnmute[chn]=0; + chnPatchChange(chn,0); + chnPressure(chn,127); + chnPitchBender(chn, 0x00, 0x40); + chnController(chn, CTL_MAIN_VOLUME,110*volumepercentage); + chnController(chn, CTL_EXT_EFF_DEPTH, 0); + chnController(chn, CTL_CHORUS_DEPTH, 0); + chnController(chn, 0x4a, 127); + } +#endif +} + +void MidiOut::setMidiMapper(MidiMapper *_map) +{ + delete map; + map=_map; +} + +void MidiOut::noteOn (uchar chn, uchar note, uchar vel) +{ + if (vel==0) + { + noteOff(chn,note,vel); + } + else + { + SEQ_MIDIOUT(device, MIDI_NOTEON + map->channel(chn)); + SEQ_MIDIOUT(device, map->key(chn,chnpatch[chn],note)); + SEQ_MIDIOUT(device, vel); + } +#ifdef MIDIOUTDEBUG + printfdebug("Note ON >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +void MidiOut::noteOff (uchar chn, uchar note, uchar vel) +{ + SEQ_MIDIOUT(device, MIDI_NOTEOFF + map->channel(chn)); + SEQ_MIDIOUT(device, map->key(chn,chnpatch[chn],note)); + SEQ_MIDIOUT(device, vel); +#ifdef MIDIOUTDEBUG + printfdebug("Note OFF >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +void MidiOut::keyPressure (uchar chn, uchar note, uchar vel) +{ + SEQ_MIDIOUT(device, MIDI_KEY_PRESSURE + map->channel(chn)); + SEQ_MIDIOUT(device, map->key(chn,chnpatch[chn],note)); + SEQ_MIDIOUT(device, vel); +} + +void MidiOut::chnPatchChange (uchar chn, uchar patch) +{ +#ifdef MIDIOUTDEBUG + printfdebug("PATCHCHANGE [%d->%d] %d -> %d\n", + chn,map->channel(chn),patch,map->patch(chn,patch)); +#endif + SEQ_MIDIOUT(device, MIDI_PGM_CHANGE + map->channel(chn)); + SEQ_MIDIOUT(device, map->patch(chn,patch)); + chnpatch[chn]=patch; +} + +void MidiOut::chnPressure (uchar chn, uchar vel) +{ + SEQ_MIDIOUT(device, MIDI_CHN_PRESSURE + map->channel(chn)); + SEQ_MIDIOUT(device, vel); + + chnpressure[chn]=vel; +} + +void MidiOut::chnPitchBender(uchar chn,uchar lsb, uchar msb) +{ + SEQ_MIDIOUT(device, MIDI_PITCH_BEND + map->channel(chn)); + /* +#ifdef AT_HOME + short pbs=((short)msb<<7) | (lsb & 0x7F); + pbs=pbs-0x2000; + short pbs2=(((long)pbs*672)/4096); + printfdebug("Pitch Bender (%d): %d -> %d \n",chn,pbs,pbs2); + pbs2=pbs2+0x2000; + lsb=pbs2 & 0x7F; + msb=(pbs2 >> 7)&0x7F; +#endif + */ + map->pitchBender(chn,lsb,msb); + SEQ_MIDIOUT(device, lsb); + SEQ_MIDIOUT(device, msb); + chnbender[chn]=(msb << 8) | (lsb & 0xFF); +} + +void MidiOut::chnController (uchar chn, uchar ctl, uchar v) +{ + SEQ_MIDIOUT(device, MIDI_CTL_CHANGE + map->channel(chn)); +#ifdef AT_HOME + if (ctl==11) ctl=7; +#endif + map->controller(chn,ctl,v); + if ((ctl==11)||(ctl==7)) + { + v=(v*volumepercentage)/100; + if (v>127) v=127; + } + + SEQ_MIDIOUT(device, ctl); + SEQ_MIDIOUT(device, v); + + chncontroller[chn][ctl]=v; +} + +void MidiOut::sysex(uchar *data, ulong size) +{ + ulong i=0; + SEQ_MIDIOUT(device, MIDI_SYSTEM_PREFIX); + while (i<size) + { + SEQ_MIDIOUT(device, *data); + data++; + i++; + } +#ifdef MIDIOUTDEBUG + printfdebug("sysex\n"); +#endif +} + +void MidiOut::allNotesOff (void) +{ + for (int i=0; i<16; i++) + { + chnController(i, 0x78, 0); + chnController(i, 0x79, 0); + }; + sync(1); +} + +void MidiOut::channelSilence (uchar chn) +{ + uchar i; + for ( i=0; i<127; i++) + { + noteOff(chn,i,0); + }; + sync(); +} + +void MidiOut::channelMute(uchar chn, int a) +{ + if (a==1) + { + chnmute[chn]=a; + channelSilence(chn); + } + else if (a==0) + { + chnmute[chn]=a; + } + /* else ignore the call to this function */ +} + +void MidiOut::seqbuf_dump (void) +{ +#ifdef HAVE_OSS_SUPPORT + if (_seqbufptr && seqfd!=-1 && seqfd!=0) + if (write (seqfd, _seqbuf, _seqbufptr) == -1) + { + printfdebug("Error writing to /dev/sequencer in MidiOut::seq_buf_dump\n"); + perror ("write /dev/sequencer in seqBufDump\n"); + exit (-1); + } + _seqbufptr = 0; +#endif +} + +void MidiOut::seqbuf_clean(void) +{ +#ifdef HAVE_OSS_SUPPORT + _seqbufptr=0; +#endif +} + +const char *MidiOut::midiMapFilename(void) +{ + return (map!=NULL) ? map->filename() : ""; +} + +const char * MidiOut::deviceName(void) const +{ + switch (deviceType()) + { + case (KMID_EXTERNAL_MIDI) : return "External Midi"; + case (KMID_SYNTH) : return "Synth"; + case (KMID_FM) : return "FM"; + case (KMID_GUS) : return "GUS"; + case (KMID_AWE) : return "AWE"; + case (KMID_ALSA) : return reinterpret_cast<const AlsaOut *>(this)->deviceName(); + } + return "Unknown"; +} + +void MidiOut::sync(int i) +{ + if (deviceType()==KMID_ALSA) { // XXX : sync should be virtual after next bic + reinterpret_cast<AlsaOut *>(this)->sync(i); + return; + } + SEQ_DUMPBUF(); +} diff --git a/libkmid/midiout.h b/libkmid/midiout.h new file mode 100644 index 000000000..93bbfff8c --- /dev/null +++ b/libkmid/midiout.h @@ -0,0 +1,251 @@ +/* midiout.h - class midiOut which handles the /dev/sequencer device + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _MIDIOUT_H +#define _MIDIOUT_H + +#include <libkmid/dattypes.h> +#include <libkmid/deviceman.h> +#include <libkmid/midimapper.h> +#include <stdio.h> + +/** + * External MIDI port output class . This class is used to send midi + * events to external midi devices. + * + * MidiOut is inherited by other MIDI devices classes + * (like SynthOut or FMOut) to support a common API. + * + * In general, you don't want to use MidiOut directly, but within a + * DeviceManager object, which is the preferred way to generate music. + * + * If you want to add support for other devices (I don't think + * there are any) you just have to create a class that inherits from MidiOut + * and create one object of your new class in + * DeviceManager::initManager(). + * + * @short Sends MIDI events to external MIDI devices + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class MidiOut +{ + private: + class MidiOutPrivate; + MidiOutPrivate *d; + + protected: + + /** + * @internal + * This is the /dev/sequencer file handler. + * Remember _not_to_close_ it on MidiOut, but just on DeviceManager + */ + int seqfd; + + int device; + + int devicetype; + + int volumepercentage; + + MidiMapper *map; + + uchar chnpatch [16]; + int chnbender [16]; + uchar chnpressure [16]; + uchar chncontroller [16][256]; + int chnmute [16]; + + int _ok; + + void seqbuf_dump (void); + void seqbuf_clean(void); + + public: + + /** + * Constructor. After constructing a MidiOut device, you must open it + * (using openDev() ). Additionally you may want to initialize it + * (with initDev() ), + */ + MidiOut(int d=0); + + /** + * Destructor. It doesn't matter if you close the device ( closeDev() ) + * before you destruct the object because in other case, it will be closed + * here. + */ + virtual ~MidiOut(); + + /** + * Opens the device. This is generally called from DeviceManager , so you + * shouldn't call this yourself (except if you created the MidiOut object + * yourself. + * @param sqfd a file descriptor of /dev/sequencer + * @see closeDev + * @see initDev + */ + virtual void openDev (int sqfd); + + /** + * Closes the device. It basically tells the device (the file descriptor) + * is going to be closed. + * @see openDev + */ + virtual void closeDev (); + + /** + * Initializes the device sending generic standard midi events and controllers, + * such as changing the patches of each channel to an Acoustic Piano (000), + * setting the volume to a normal value, etc. + */ + virtual void initDev (); + + /** + * @return the device type of the object. This is to identify the + * inherited class that a given object is polymorphed to. + * The returned value is one of these : + * + * @li KMID_EXTERNAL_MIDI if it's a MidiOut object + * @li KMID_SYNTH if it's a SynthOut object (as an AWE device) + * @li KMID_FM if it's a FMOut object + * @li KMID_GUS if it's a GUSOut object + * + * which are defined in midispec.h + * + * @see deviceName + */ + int deviceType () const { return devicetype; } + + /** + * Returns the name and type of this MIDI device. + * @see deviceType + */ + const char * deviceName (void) const; + + /** + * Sets a MidiMapper object to be used to modify the midi events before + * sending them. + * + * @param map the MidiMapper to use. + * + * @see MidiMapper + * @see midiMapFilename + */ + void setMidiMapper ( MidiMapper *map ); + + /** + * See DeviceManager::noteOn() + */ + virtual void noteOn ( uchar chn, uchar note, uchar vel ); + + /** + * See DeviceManager::noteOff() + */ + virtual void noteOff ( uchar chn, uchar note, uchar vel ); + + /** + * See DeviceManager::keyPressure() + */ + virtual void keyPressure ( uchar chn, uchar note, uchar vel ); + + /** + * See DeviceManager::chnPatchChange() + */ + virtual void chnPatchChange ( uchar chn, uchar patch ); + + /** + * See DeviceManager::chnPressure() + */ + virtual void chnPressure ( uchar chn, uchar vel ); + + /** + * See DeviceManager::chnPitchBender() + */ + virtual void chnPitchBender ( uchar chn, uchar lsb, uchar msb ); + + /** + * See DeviceManager::chnController() + */ + virtual void chnController ( uchar chn, uchar ctl , uchar v ); + + /** + * See DeviceManager::sysex() + */ + virtual void sysex ( uchar *data,ulong size); + + /** + * Send a All Notes Off event to every channel + */ + void allNotesOff(void); + + /** + * Mutes all notes being played on a given channel. + * @param chn the channel + */ + virtual void channelSilence ( uchar chn ); + + /** + * Mute or "unmute" a given channel . + * @param chn channel to work on + * @param b if true, the device will ignore subsequent notes played on the chn + * channel, and mute all notes being played on it. If b is false, the channel + * is back to work. + */ + virtual void channelMute ( uchar chn, int b ); + + /** + * Change all channel volume events multiplying it by this percentage correction + * Instead of forcing a channel to a fixed volume, this method allows to + * music to fade out even when it was being played softly. + * @param volper is an integer value, where 0 is quiet, 100 is used to send + * an unmodified value, 200 play music twice louder than it should, etc. + */ + virtual void setVolumePercentage ( int volper ) + { volumepercentage = volper; } + + /** + * Returns true if everything's ok and false if there has been any problem + */ + int ok (void) + { if (seqfd<0) return 0; + return (_ok>0); + } + + /** + * Returns the path to the file where the current used MidiMapper object + * reads the configuration from, or an empty string if there's no MidiMapper. + */ + const char *midiMapFilename (); + + /** + * Sends the buffer to the device and returns when it's played, so you can + * synchronize + * XXX: sync should be virtual after next bic release + */ + void sync(int i=0); + +}; + +#endif diff --git a/libkmid/midispec.h b/libkmid/midispec.h new file mode 100644 index 000000000..3f60d49f2 --- /dev/null +++ b/libkmid/midispec.h @@ -0,0 +1,60 @@ +/* midispec.h - Some definitions to make the code more readable + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ + +#ifndef _MIDISPEC_H +#define _MIDISPEC_H + +#define META_EVENT 0xFF + +#define ME_TRACK_SEQ_NUMBER 0x00 +#define ME_TEXT 0x01 +#define ME_COPYRIGHT 0x02 +#define ME_SEQ_OR_TRACK_NAME 0x03 +#define ME_TRACK_INSTR_NAME 0x04 +#define ME_LYRIC 0x05 +#define ME_MARKER 0x06 +#define ME_CUE_POINT 0x07 +#define ME_CHANNEL_PREFIX 0x20 +#define ME_MIDI_PORT 0x21 +#define ME_SET_TEMPO 0x51 +#define ME_SMPTE_OFFSET 0x54 +#define ME_TIME_SIGNATURE 0x58 +#define ME_KEY_SIGNATURE 0x59 +/* sf=sharps/flats (-7=7 flats, 0=key of C, 7=7 sharps) + mi=major/minor (0=major, 1=minor) +*/ + +#define ME_END_OF_TRACK 0x2F + + +#define PERCUSSION_CHANNEL 9 + +#define KMID_EXTERNAL_MIDI 1 +#define KMID_SYNTH 2 +#define KMID_FM 3 +#define KMID_GUS 4 +#define KMID_AWE 5 //For future class aweOut +#define KMID_ALSA 6 + +#endif diff --git a/libkmid/midistat.cc b/libkmid/midistat.cc new file mode 100644 index 000000000..29d1436c8 --- /dev/null +++ b/libkmid/midistat.cc @@ -0,0 +1,115 @@ +/************************************************************************** + + midistat.cc - class MidiStatus, change it internally and then send it. + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "midistat.h" +#include "deviceman.h" +#include "sndcard.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +extern int MT32toGM[128]; + +MidiStatus::MidiStatus() +{ + int i; + tempo=1000000; + for (int chn=0;chn<16;chn++) + { + chn_patch[chn]=0; + chn_bender[chn]=0x4000; + chn_pressure[chn]=127; + for (i=0;i<256;i++) + chn_controller[chn][i]=0; + chn_controller[chn][CTL_MAIN_VOLUME]=127; + chn_controller[chn][11]=127; + chn_controller[chn][0x4a]=127; + chn_lastisvolumeev[chn]=1; + } +} + +MidiStatus::~MidiStatus() +{ +} + +// void noteOn ( uchar chn, uchar note, uchar vel ); +// void noteOff ( uchar chn, uchar note, uchar vel ); + +void MidiStatus::chnPatchChange ( uchar chn, uchar patch ) +{ + chn_patch[chn]=patch; +} + +void MidiStatus::chnPressure ( uchar chn, uchar vel ) +{ + chn_pressure[chn]=vel; +} + +void MidiStatus::chnPitchBender ( uchar chn, uchar lsb, uchar msb ) +{ + chn_bender[chn]=((int)msb<<8|lsb); +} + +void MidiStatus::chnController ( uchar chn, uchar ctl , uchar v ) +{ + if (ctl==7) chn_lastisvolumeev[chn]=1; + else if (ctl==11) chn_lastisvolumeev[chn]=0; + + chn_controller[chn][ctl]=v; +} + +void MidiStatus::tmrSetTempo(int v) +{ + tempo=v; +} + +void MidiStatus::sendData(DeviceManager *midi,int gm) +{ + for (int chn=0;chn<16;chn++) + { +#ifdef MIDISTATDEBUG + printf("Restoring channel %d\n",chn); +#endif + midi->chnPatchChange(chn, + (gm==1)?(chn_patch[chn]):(MT32toGM[chn_patch[chn]])); + midi->chnPitchBender(chn,chn_bender[chn]&0xFF,chn_bender[chn]>>8); + midi->chnPressure(chn,chn_pressure[chn]); + if (chn_lastisvolumeev[chn]) + { + midi->chnController(chn,11,chn_controller[chn][11]); + midi->chnController(chn,CTL_MAIN_VOLUME,chn_controller[chn][CTL_MAIN_VOLUME]); + } else { + midi->chnController(chn,CTL_MAIN_VOLUME,chn_controller[chn][CTL_MAIN_VOLUME]); + midi->chnController(chn,11,chn_controller[chn][11]); + } + /* + for (int i=0;i<256;i++) + midi->chnController(chn,i,chn_controller[chn][i]); + */ + } + midi->tmrSetTempo(tempo); + midi->sync(); +} diff --git a/libkmid/midistat.h b/libkmid/midistat.h new file mode 100644 index 000000000..17dda1269 --- /dev/null +++ b/libkmid/midistat.h @@ -0,0 +1,143 @@ +/* midistat.h - class midiStat, change it internally and then send it. + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + + ***************************************************************************/ +#ifndef _MIDISTAT_H +#define _MIDISTAT_H + +#include <libkmid/dattypes.h> + +/** + * Stores the status of a MIDI device . That is, current patch in each channel, + * controller settings, pitch bender value, etc. + * + * This is used to "play" with all those values and then send them to the + * MIDI device just by using sendData() + * + * @short Stores the MIDI status. + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class MidiStatus +{ + private: + class MidiStatusPrivate; + MidiStatusPrivate *d; + + ulong tempo; + + unsigned char chn_patch [16]; + int chn_bender [16]; + unsigned char chn_pressure[16]; + unsigned char chn_controller[16][256]; + + int chn_lastisvolumeev[16]; + + public: + /** + * Constructor. + */ + MidiStatus(); + + /** + * Destructor. + */ + ~MidiStatus(); + + + // void noteOn ( uchar chn, uchar note, uchar vel ); + // void noteOff ( uchar chn, uchar note, uchar vel ); + + /** + * Stores a new value for the key aftertouch. + * @see MidiOut::keyPressure() + */ + void keyPressure ( uchar chn, uchar note, uchar vel ); + + /** + * Stores a new patch in channel @p chn. + * @see chnPatch() + * @see MidiOut::chnPatchChange() + */ + void chnPatchChange ( uchar chn, uchar patch ); + + /** + * Returns the patch currently used in channel @p chn. + */ + uchar chnPatch ( uchar chn ) { return chn_patch[chn]; } + + /** + * Stores a new channel pressure value in channel @p chn. + * @see MidiOut::chnPressure() + */ + void chnPressure ( uchar chn, uchar vel ); + + /** + * Returns the pressure value currently used in channel @p chn. + */ + uchar chnPressure ( uchar chn ) { return chn_pressure[chn]; } + + /** + * Stores a new pitch bender value in channel chn + */ + void chnPitchBender ( uchar chn, uchar lsb, uchar msb ); + + /** + * Returns the pitch bender value used in channel @p chn + */ + int chnPitchBender ( uchar chn) { return chn_bender[chn]; } + + /** + * Stores a new value for controller @p ctl in channel @p chn. + */ + void chnController ( uchar chn, uchar ctl , uchar v ); + + /** + * Returns the value used for controller @p ctl in channel @p chn + */ + uchar chnController ( uchar chn, uchar ctl ) + { return chn_controller[chn][ctl]; } + + /** + * Stores a sysex message that will be send in the next call to sendData + */ + void sysex ( uchar *data, ulong size); + + /** + * Sets the tempo. + * + * @see DeviceManager::tmrSetTempo() + */ + void tmrSetTempo ( int v ); + + + /** + * Sends the current MIDI state to the DeviceManager object used as + * parameter (you should have already set the default device to the one you + * want to use). The @p gm parameter specifies if the patches used follow + * the GM standard (1), or follow the MT32 standard (0), in which case, they + * will be converted to GM before being sent. + */ + void sendData ( class DeviceManager *midi, int gm=1 ); +}; + +#endif diff --git a/libkmid/mt32togm.cc b/libkmid/mt32togm.cc new file mode 100644 index 000000000..a59eb959c --- /dev/null +++ b/libkmid/mt32togm.cc @@ -0,0 +1,18 @@ +#include "mt32togm.h" + +int MT32toGM[128] = +{ + 0, 1, 2, 4, 4, 5, 5, 3, 16, 16, + 16, 16, 19, 19, 19, 21, 6, 6, 6, 7, + 7, 7, 8, 8, 62, 57, 63, 58, 38, 38, + 39, 39, 88, 33, 52, 35, 97, 100, 38, 39, + 14, 102, 68, 103, 44, 92, 46, 80, 48, 49, + 51, 45, 40, 40, 42, 42, 43, 46, 46, 24, + 25, 28, 27, 104, 32, 32, 34, 33, 36, 37, + 39, 35, 79, 73, 76, 72, 74, 75, 64, 65, + 66, 67, 71, 71, 69, 70, 60, 22, 56, 59, + 57, 63, 60, 60, 58, 61, 61, 11, 11, 99, + 100, 9, 14, 13, 12, 107, 106, 77, 78, 78, + 76, 111, 47, 117, 127, 115, 118, 116, 118, 126, + 121, 121, 55, 124, 120, 125, 126, 127 +}; diff --git a/libkmid/mt32togm.h b/libkmid/mt32togm.h new file mode 100644 index 000000000..1da38a2f0 --- /dev/null +++ b/libkmid/mt32togm.h @@ -0,0 +1,31 @@ +/* mt32togm.h - the translation map from MT32 patches to GM patches + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _MT32TOGM_H +#define _MT32TOGM_H + +#include <kdelibs_export.h> + +extern int KMID_EXPORT MT32toGM[128]; + +#endif diff --git a/libkmid/notearray.cc b/libkmid/notearray.cc new file mode 100644 index 000000000..70652f16e --- /dev/null +++ b/libkmid/notearray.cc @@ -0,0 +1,122 @@ +/************************************************************************** + + notearray.cc - NoteArray class, which holds an array of notes + This file is part of LibKMid 0.9.5 + Copyright (C) 1998,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ + +#include "notearray.h" +#include <string.h> + +NoteArray::NoteArray(void) +{ + totalAllocated=50; + data=new noteCmd[totalAllocated]; + lastAdded=0L; +} + +NoteArray::~NoteArray() +{ + delete data; + totalAllocated=0; +} + +NoteArray::noteCmd *NoteArray::pointerTo(ulong pos) +{ + if (pos<totalAllocated) return &data[pos]; + while (pos>=totalAllocated) + { + noteCmd *tmp=new noteCmd[totalAllocated*2]; + memcpy(tmp,data,sizeof(noteCmd)*totalAllocated); + delete data; + data=tmp; + totalAllocated*=2; + } + return &data[pos]; +} + +void NoteArray::at(ulong pos, ulong ms,int chn,int cmd,int note) +{ + noteCmd *tmp=pointerTo(pos); + tmp->ms=ms; + tmp->chn=chn; + tmp->cmd=cmd; + tmp->note=note; +} + +void NoteArray::at(ulong pos, noteCmd s) +{ + noteCmd *tmp=pointerTo(pos); + tmp->ms=s.ms; + tmp->chn=s.chn; + tmp->cmd=s.cmd; + tmp->note=s.note; +} + +NoteArray::noteCmd NoteArray::at(int pos) +{ + return *pointerTo(pos); +} + +void NoteArray::add(ulong ms,int chn,int cmd,int note) +{ + if (lastAdded==NULL) + { + lastAdded=data; + last=0; + } + else + { + last++; + if (last==totalAllocated) lastAdded=pointerTo(totalAllocated); + else lastAdded++; + } + lastAdded->ms=ms; + lastAdded->chn=chn; + lastAdded->cmd=cmd; + lastAdded->note=note; +} + +void NoteArray::next(void) +{ + if (it==lastAdded) {it=NULL;return;}; + it++; +} + +void NoteArray::moveIteratorTo(ulong ms,int *pgm) +{ + noteCmd *ncmd; + iteratorBegin(); + ncmd=get(); + int pgm2[16]; + for (int j=0;j<16;j++) pgm2[j]=0; + while ((ncmd!=NULL)&&(ncmd->ms<ms)) + { + if (ncmd->cmd==2) pgm2[ncmd->chn]=ncmd->note; + next(); + ncmd=get(); + } + if (pgm!=NULL) + { + for (int i=0;i<16;i++) pgm[i]=pgm2[i]; + } +} diff --git a/libkmid/notearray.h b/libkmid/notearray.h new file mode 100644 index 000000000..d462b06b5 --- /dev/null +++ b/libkmid/notearray.h @@ -0,0 +1,145 @@ +/* notearray.h - NoteArray class, which holds an array of notes + This file is part of LibKMid 0.9.5 + Copyright (C) 1998,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef NOTEARRAY_H +#define NOTEARRAY_H + +#include <libkmid/dattypes.h> +#include <kdelibs_export.h> + +/** + * Holds a resizeable array of note on/off and patch change events. It can + * increase it size, but it doesn't decreases (until destruction :-) ) + * + * @short Stores an array of note on/off events + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class KMID_EXPORT NoteArray +{ + private: + class NoteArrayPrivate; + NoteArrayPrivate *d; + + public: + struct noteCmd { + /** + * ms from beginning of song + */ + ulong ms; + + /** + * The channel + */ + int chn; + + /** + * 0 note off, 1 note on, 2 change patch + */ + int cmd; + + /** + * The note. + * + * If cmd==2, then the patch is stored in "note" + */ + int note; + }; + + private: + noteCmd *data; + ulong totalAllocated; + + ulong last; + noteCmd *lastAdded; + + /** + * @internal + * The iterator + */ + noteCmd *it; + + noteCmd *pointerTo(ulong pos); + + public: + /** + * Constructor. Initializes internal variables. + */ + NoteArray(void); + /** + * Destructor. + */ + ~NoteArray(); + + /** + * Adds (or modifies) an event in the given position . + * + * Note that this has nothing to do with what is being played, this just + * modifies an internal array. + */ + void at(ulong pos, ulong ms,int chn,int cmd,int note); + + /** + * A convenience function, which differs from the above in the parameters + * it accepts. + */ + void at(ulong pos, noteCmd s); + + /** + * Returns the note event at a given position. + */ + noteCmd at(int pos); + + /** + * Adds a note/patch event at a given millisecond. + * + * Note: This method always appends at the end of the list. + */ + void add(ulong ms,int chn,int cmd,int note); + + /** + * Initializes the iterator. + * + * @see get() + * @see next() + */ + void iteratorBegin(void) { it=data; } + + /** + * Get the command currently pointed to by the iterator. + */ + noteCmd *get(void) { return it; } + + /** + * Advances the iterator to the next position. + */ + void next(void); + + /** + * Calls next() until the next event is over ms milliseconds + * and puts in @p pgm[16] the instruments used at this moment. + */ + void moveIteratorTo(ulong ms,int *pgm=NULL); +}; + +#endif diff --git a/libkmid/player.cc b/libkmid/player.cc new file mode 100644 index 000000000..a68a9b147 --- /dev/null +++ b/libkmid/player.cc @@ -0,0 +1,959 @@ +/************************************************************************** + + player.cc - class MidiPlayer. Plays a set of tracks + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + $Id$ + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "player.h" +#include "sndcard.h" +#include "midispec.h" +#include <string.h> +#include <unistd.h> +#include <sys/time.h> +#include "midistat.h" +#include "mt32togm.h" + +//#define PLAYERDEBUG +//#define GENERAL_DEBUG_MESSAGES + +#define T2MS(ticks) (((double)ticks)*(double)60000L)/((double)tempoToMetronomeTempo(tempo)*(double)info->ticksPerCuarterNote) + +#define MS2T(ms) (((ms)*(double)tempoToMetronomeTempo(tempo)*(double)info->ticksPerCuarterNote)/((double)60000L)) + +#define REMOVEDUPSTRINGS + +MidiPlayer::MidiPlayer(DeviceManager *midi_,PlayerController *pctl) +{ + midi=midi_; + info=NULL; + tracks=NULL; + songLoaded=0; + ctl=pctl; + spev=NULL; + na=NULL; + parsesong=true; + generatebeats=false; +} + +MidiPlayer::~MidiPlayer() +{ + removeSpecialEvents(); + removeSong(); +} + +void MidiPlayer::removeSong(void) +{ + if ((songLoaded)&&(tracks!=NULL)) + { +#ifdef PLAYERDEBUG + printf("Removing song from memory\n"); +#endif + int i=0; + while (i<info->ntracks) + { + if (tracks[i]!=NULL) delete tracks[i]; + i++; + } + delete tracks; + tracks=NULL; + if (info!=NULL) + { + delete info; + info=NULL; + } + } + songLoaded=0; +} + +int MidiPlayer::loadSong(const char *filename) +{ + removeSong(); +#ifdef PLAYERDEBUG + printf("Loading Song : %s\n",filename); +#endif + info=new MidiFileInfo; + int ok; + tracks=readMidiFile(filename,info,ok); + if (ok<0) return ok; + if (tracks==NULL) return -4; + + parseInfoData(info,tracks,ctl->ratioTempo); + + if (parsesong) + { + parseSpecialEvents(); + if (generatebeats) generateBeats(); + } + + songLoaded=1; + return 0; +} + +void MidiPlayer::insertBeat(SpecialEvent *ev,ulong ms,int num,int den) +{ + SpecialEvent *beat=new SpecialEvent; + beat->next=ev->next; + ev->next=beat; + beat->id=1; + beat->type=7; + beat->absmilliseconds=ms; + beat->num=num; + beat->den=den; +} + + +void MidiPlayer::generateBeats(void) +{ +#ifdef PLAYERDEBUG + printf("player::Generating Beats...\n"); +#endif + + if (spev==NULL) return; + SpecialEvent *ev=spev; + SpecialEvent *nextev=ev->next; + ulong tempo=(ulong)(500000 * ctl->ratioTempo); + int i=1; + int num=4; + int den=4; + // ulong beatstep=((double)tempo*4/(den*1000)); + // ulong beatstep=T2MS(info->ticksPerCuarterNote*(4/den)); + double ticksleft=(((double)info->ticksPerCuarterNote*4)/den); + + double beatstep=T2MS(ticksleft); + double nextbeatms=0; + double lastbeatms=0; + double measurems=0; + + while (nextev!=NULL) + { + switch (ev->type) + { + case (0): // End of list + { + };break; + case (1): // Text + case (2): // Lyrics + { + };break; + case (3): // Change Tempo + { + lastbeatms=ev->absmilliseconds; + ticksleft=MS2T(nextbeatms-lastbeatms); + tempo=ev->tempo; + nextbeatms=lastbeatms+T2MS(ticksleft); + // printf("Change at %lu to %d\n",ev->absmilliseconds,ev->tempo); + // beatstep=((double)tempo*4/(den*1000)); + beatstep=T2MS(((static_cast<double>(info->ticksPerCuarterNote)*4)/den)); + };break; + case (6): // Change number of beats per measure + { + num=ev->num; + i=1; + den=ev->den; + // printf("Change at %lu to %d/%d\n",ev->absmilliseconds,num,den); + // beatstep=((double)tempo*4/(den*1000)); + // beatstep=T2MS(info->ticksPerCuarterNote*(4/den)); + beatstep=T2MS((((double)info->ticksPerCuarterNote*4)/den)); + nextbeatms=ev->absmilliseconds; + };break; + }; + if (nextev->absmilliseconds>nextbeatms) + { + //printf("Adding %d,%d\n",num,tot); + //printf("beat at %g , %d/%d\n",nextbeatms,i,num); + //printf(" %ld %d\n",nextev->absmilliseconds,nextev->type); + if (i == 1) { + measurems=nextbeatms; + } + insertBeat(ev, static_cast<unsigned long>(nextbeatms), i++, num); + if (i > num) { + i=1; + } + lastbeatms=nextbeatms; + nextbeatms+=beatstep; + // nextbeatms=measurems+beatstep*i; + + ticksleft = ( (static_cast<double>(info->ticksPerCuarterNote)*4) / den); + + } + + ev=ev->next; + nextev=ev->next; + } + + /* ev==NULL doesn't indicate the end of the song, so continue generating beats */ + + if (ev!=NULL) + { + if (ev->type==0) + { + ev=spev; + /* Looking if ev->next is NULL is not needed because + we are sure that a ev->type == 0 exists, we just have + to assure that the first spev is not the only one */ + if (ev->next!=NULL) + while (ev->next->type!=0) ev=ev->next; + } + while (nextbeatms<info->millisecsTotal) + { + // printf("beat2 at %g , %d/%d\n",nextbeatms,i,num); + if (i==1) measurems=nextbeatms; + insertBeat(ev, static_cast<unsigned long>(nextbeatms), i++, num); + if (i>num) i=1; + nextbeatms+=beatstep; + ev=ev->next; + } + } + + /* Regenerate IDs */ + + ev=spev; + i=1; + while (ev!=NULL) + { + ev->id=i++; + ev=ev->next; + } + + +#ifdef PLAYERDEBUG + printf("player::Beats Generated\n"); +#endif + +} + +void MidiPlayer::removeSpecialEvents(void) +{ + SpecialEvent * ev=spev; + while (spev!=NULL) + { + ev=spev->next; + delete spev; + spev=ev; + } + delete na; + na=0; +} + +void MidiPlayer::parseSpecialEvents(void) +{ +#ifdef PLAYERDEBUG + printf("player::Parsing...\n"); +#endif + removeSpecialEvents(); + spev=new SpecialEvent; + if (spev==NULL) return; + SpecialEvent *pspev=spev; + pspev->type=0; + pspev->ticks=0; + if (na) delete na; + na=new NoteArray(); + if (!na) { delete spev; spev=0L; return; }; + int trk; + int minTrk; + double minTime=0; + double maxTime; + ulong tempo=(ulong)(500000 * (ctl->ratioTempo)); + ulong firsttempo=0; + for (int i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + tracks[i]->changeTempo(tempo); + } + MidiEvent *ev=new MidiEvent; + //ulong mspass; + double prevms=0; + int spev_id=1; + int j; + int parsing=1; +#ifdef REMOVEDUPSTRINGS + char lasttext[1024]; + ulong lasttexttime=0; + lasttext[0]=0; + int lasttexttype=0; +#endif + while (parsing) + { + prevms=minTime; + trk=0; + minTrk=0; + maxTime=minTime + 2 * 60000L; + minTime=maxTime; + parsing=0; + while (trk<info->ntracks) + { + if (tracks[trk]->absMsOfNextEvent()<minTime) + { + minTrk=trk; + minTime=tracks[minTrk]->absMsOfNextEvent(); + parsing=1; + } + trk++; + } + // if ((minTime==maxTime)) + if (parsing==0) + { + // parsing=0; +#ifdef PLAYERDEBUG + printf("END of parsing\n"); +#endif + } + else + { + // mspass=(ulong)(minTime-prevms); + trk=0; + while (trk<info->ntracks) + { + tracks[trk]->currentMs(minTime); + trk++; + } + } + trk=minTrk; + tracks[trk]->readEvent(ev); + switch (ev->command) + { + case (MIDI_NOTEON) : + if (ev->vel==0) na->add((ulong)minTime,ev->chn,0, ev->note); + else na->add((ulong)minTime,ev->chn,1,ev->note); + break; + case (MIDI_NOTEOFF) : + na->add((ulong)minTime,ev->chn,0, ev->note); + break; + case (MIDI_PGM_CHANGE) : + na->add((ulong)minTime,ev->chn, 2,ev->patch); + break; + case (MIDI_SYSTEM_PREFIX) : + { + if ((ev->command|ev->chn)==META_EVENT) + { + switch (ev->d1) + { + case (1) : + case (5) : + { + if (pspev!=NULL) + { + pspev->absmilliseconds=(ulong)minTime; + pspev->type=ev->d1; + pspev->id=spev_id++; +#ifdef PLAYERDEBUG + printf("ev->length %ld\n",ev->length); + +#endif + strncpy(pspev->text,(char *)ev->data, + (ev->length>= sizeof(lasttext))? sizeof(lasttext)-1 : (ev->length) ); + pspev->text[(ev->length>= sizeof(lasttext))? sizeof(lasttext)-1:(ev->length)]=0; +#ifdef PLAYERDEBUG + printf("(%s)(%s)\n",pspev->text,lasttext); +#endif +#ifdef REMOVEDUPSTRINGS + if ((strcmp(pspev->text,lasttext)!=0)||(pspev->absmilliseconds!=lasttexttime)||(pspev->type!=lasttexttype)) + { + lasttexttime=pspev->absmilliseconds; + lasttexttype=pspev->type; + strncpy(lasttext, pspev->text, 1024); + lasttext[sizeof(lasttext)-1] = 0; +#endif + pspev->next=new SpecialEvent; +#ifdef PLAYERDEBUG + if (pspev->next==NULL) printf("pspev->next=NULL\n"); +#endif + pspev=pspev->next; +#ifdef REMOVEDUPSTRINGS + } +#endif + } + } + break; + case (ME_SET_TEMPO) : + { + if (pspev!=NULL) + { + pspev->absmilliseconds=(ulong)minTime; + pspev->type=3; + pspev->id=spev_id++; + tempo=(ulong)(((ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2])) * ctl->ratioTempo); + pspev->tempo=tempo; + if (firsttempo==0) firsttempo=tempo; + for (j=0;j<info->ntracks;j++) + { + tracks[j]->changeTempo(tempo); + } + pspev->next=new SpecialEvent; + pspev=pspev->next; + } + } + break; + case (ME_TIME_SIGNATURE) : + { + if (pspev!=NULL) + { + pspev->absmilliseconds=(ulong)minTime; + pspev->type=6; + pspev->id=spev_id++; + pspev->num=ev->d2; + pspev->den=ev->d3; + pspev->next=new SpecialEvent; + pspev=pspev->next; + } + } + break; + } + } + } + break; + } + } + + delete ev; + pspev->type=0; + pspev->absmilliseconds=(ulong)prevms; + pspev->next=NULL; + if (firsttempo==0) firsttempo=tempo; + ctl->tempo=firsttempo; + + //writeSPEV(); + for (int i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + } +} + +/* +NoteArray *MidiPlayer::parseNotes(void) +{ +#ifdef PLAYERDEBUG + printf("player::Parsing Notes...\n"); +#endif + NoteArray *na=new NoteArray(); + int trk; + int minTrk; + double minTime=0; + double maxTime; + for (int i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + }; + ulong tempo=1000000; + ulong tmp; + Midi_event *ev=new Midi_event; + //ulong mspass; + double prevms=0; + int j; + int parsing=1; + while (parsing) + { + prevms=minTime; + trk=0; + minTrk=0; + maxTime=minTime + 2 * 60000L; + minTime=maxTime; + while (trk<info->ntracks) + { + if (tracks[trk]->absMsOfNextEvent()<minTime) + { + minTrk=trk; + minTime=tracks[minTrk]->absMsOfNextEvent(); + }; + trk++; + }; + if ((minTime==maxTime)) + { + parsing=0; +#ifdef PLAYERDEBUG + printf("END of parsing\n"); +#endif + } + else + { + // mspass=(ulong)(minTime-prevms); + trk=0; + while (trk<info->ntracks) + { + tracks[trk]->currentMs(minTime); + trk++; + }; + }; + trk=minTrk; + tracks[trk]->readEvent(ev); + if (ev->command==MIDI_NOTEON) + { + if (ev->vel==0) {printf("note off at %g\n",minTime);na->add((ulong)minTime,ev->chn,0, ev->note);} + else {printf("note on at %g\n",minTime);na->add((ulong)minTime,ev->chn,1,ev->note);} + } + else + if (ev->command==MIDI_NOTEOFF) na->add((ulong)minTime,ev->chn,0, ev->note); + if (ev->command==MIDI_PGM_CHANGE) na->add((ulong)minTime,ev->chn, 2,ev->patch); + if (ev->command==MIDI_SYSTEM_PREFIX) + { + if (((ev->command|ev->chn)==META_EVENT)&&(ev->d1==ME_SET_TEMPO)) + { + tempo=(ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2]); + for (j=0;j<info->ntracks;j++) + { + tracks[j]->changeTempo(tempo); + }; + }; + }; + + }; + + delete ev; + for (int i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + }; + return na; +}; +*/ + +void MidiPlayer::play(bool calloutput,void output(void)) +{ +#ifdef PLAYERDEBUG + printf("Playing...\n"); +#endif + + if (midi->midiPorts()+midi->synthDevices()==0) + { + fprintf(stderr,"Player :: There are no midi ports !\n"); + ctl->error=1; + return; + } + + midi->openDev(); + if (midi->ok()==0) + { + fprintf(stderr,"Player :: Couldn't play !\n"); + ctl->error=1; + return; + } + midi->setVolumePercentage(ctl->volumepercentage); + midi->initDev(); + // parsePatchesUsed(tracks,info,ctl->gm); + midi->setPatchesToUse(info->patchesUsed); + + int trk; + int minTrk; + double minTime=0; + double maxTime; + int i; + ulong tempo=(ulong)(500000 * ctl->ratioTempo); + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + tracks[i]->changeTempo(tempo); + } + + midi->tmrStart(info->ticksPerCuarterNote); + MidiEvent *ev=new MidiEvent; + ctl->ev=ev; + ctl->ticksTotal=info->ticksTotal; + ctl->ticksPlayed=0; + //ctl->millisecsPlayed=0; + ulong ticksplayed=0; + double absTimeAtChangeTempo=0; + double absTime=0; + double diffTime=0; + MidiStatus *midistat; + //ulong mspass; + double prevms=0; + int j; + int halt=0; + ctl->tempo=tempo; + ctl->num=4; + ctl->den=4; + int playing; + ctl->paused=0; + if ((ctl->message!=0)&&(ctl->message & PLAYER_SETPOS)) + { + ctl->moving=1; + ctl->message&=~PLAYER_SETPOS; + midi->sync(1); + midi->tmrStop(); + midi->closeDev(); + midistat = new MidiStatus(); + setPos(ctl->gotomsec,midistat); + minTime=ctl->gotomsec; + prevms=(ulong)minTime; + midi->openDev(); + midi->tmrStart(info->ticksPerCuarterNote); + diffTime=ctl->gotomsec; + midistat->sendData(midi,ctl->gm); + delete midistat; + midi->setPatchesToUse(info->patchesUsed); + ctl->moving=0; + } else + for (i=0;i<16;i++) + { + if (ctl->forcepgm[i]) + { + midi->chnPatchChange(i, ctl->pgm[i]); + } + } + + timeval begintv; + gettimeofday(&begintv, NULL); + ctl->beginmillisec=begintv.tv_sec*1000+begintv.tv_usec/1000; + ctl->OK=1; + ctl->playing=playing=1; + + while (playing) + { + /* + if (ctl->message!=0) + { + if (ctl->message & PLAYER_DOPAUSE) + { + diffTime=minTime; + ctl->message&=~PLAYER_DOPAUSE; + midi->sync(1); + midi->tmrStop(); + ctl->paused=1; + midi->closeDev(); + while ((ctl->paused)&&(!(ctl->message&PLAYER_DOSTOP)) + &&(!(ctl->message&PLAYER_HALT))) sleep(1); + midi->openDev(); + midi->tmrStart(); + ctl->OK=1; + printf("Continue playing ... \n"); + }; + if (ctl->message & PLAYER_DOSTOP) + { + ctl->message&=~PLAYER_DOSTOP; + playing=0; + }; + if (ctl->message & PLAYER_HALT) + { + ctl->message&=~PLAYER_HALT; + playing=0; + halt=1; + }; + if (ctl->message & PLAYER_SETPOS) + { + ctl->moving=1; + ctl->message&=~PLAYER_SETPOS; + midi->sync(1); + midi->tmrStop(); + midi->closeDev(); + midistat = new midiStat(); + SetPos(ctl->gotomsec,midistat); + minTime=ctl->gotomsec; + prevms=(ulong)minTime; + midi->openDev(); + midi->tmrStart(); + diffTime=ctl->gotomsec; + ctl->moving=0; + midistat->sendData(midi,ctl->gm); + delete midistat; + ctl->OK=1; + while (ctl->OK==1) ; + ctl->moving=0; + }; + }; + */ + prevms=minTime; + // ctl->millisecsPlayed=minTime; + trk=0; + minTrk=0; + maxTime=minTime + 120000L /* milliseconds */; + minTime=maxTime; + playing=0; + while (trk<info->ntracks) + { + if (tracks[trk]->absMsOfNextEvent()<minTime) + { + minTrk=trk; + minTime=tracks[minTrk]->absMsOfNextEvent(); + playing=1; + } + trk++; + } +#ifdef PLAYERDEBUG + printf("minTime %g\n",minTime); +#endif + // if ((minTime==maxTime)/* || (minTicks> 60000L)*/) + if (playing==0) + { + // playing=0; +#ifdef PLAYERDEBUG + printf("END of playing\n"); +#endif + } + else + { + // mspass=(ulong)(minTime-prevms); + trk=0; + while (trk<info->ntracks) + { + tracks[trk]->currentMs(minTime); + trk++; + } + midi->wait(minTime-diffTime); + } + trk=minTrk; + tracks[trk]->readEvent(ev); + switch (ev->command) + { + case (MIDI_NOTEON) : + midi->noteOn(ev->chn, ev->note, ev->vel);break; + case (MIDI_NOTEOFF): + midi->noteOff(ev->chn, ev->note, ev->vel);break; + case (MIDI_KEY_PRESSURE) : + midi->keyPressure(ev->chn, ev->note,ev->vel);break; + case (MIDI_PGM_CHANGE) : + if (!ctl->forcepgm[ev->chn]) + midi->chnPatchChange(ev->chn, (ctl->gm==1)?(ev->patch):(MT32toGM[ev->patch]));break; + case (MIDI_CHN_PRESSURE) : + midi->chnPressure(ev->chn, ev->vel);break; + case (MIDI_PITCH_BEND) : + midi->chnPitchBender(ev->chn, ev->d1,ev->d2);break; + case (MIDI_CTL_CHANGE) : + midi->chnController(ev->chn, ev->ctl,ev->d1);break; + case (MIDI_SYSTEM_PREFIX) : + if ((ev->command|ev->chn)==META_EVENT) + { + if ((ev->d1==5)||(ev->d1==1)) + { + ctl->SPEVplayed++; + } + if (ev->d1==ME_SET_TEMPO) + { + absTimeAtChangeTempo=absTime; + ticksplayed=0; + ctl->SPEVplayed++; + tempo=(ulong)(((ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2]))*ctl->ratioTempo); +#ifdef PLAYERDEBUG + printf("Tempo : %ld %g (ratio : %g)\n",tempo,tempoToMetronomeTempo(tempo),ctl->ratioTempo); +#endif + midi->tmrSetTempo((int)tempoToMetronomeTempo(tempo)); + ctl->tempo=tempo; + for (j=0;j<info->ntracks;j++) + { + tracks[j]->changeTempo(tempo); + } + } + if (ev->d1==ME_TIME_SIGNATURE) + { + ctl->num=ev->d2; + ctl->den=ev->d3; + ctl->SPEVplayed++; + } + } + break; + } + if (calloutput) + { + midi->sync(); + output(); + } + + } + ctl->ev=NULL; + delete ev; +#ifdef PLAYERDEBUG + printf("Syncronizing ...\n"); +#endif + if (halt) + midi->sync(1); + else + midi->sync(); +#ifdef PLAYERDEBUG + printf("Closing device ...\n"); +#endif + midi->allNotesOff(); + midi->closeDev(); + ctl->playing=0; +#ifdef PLAYERDEBUG + printf("Bye...\n"); +#endif + ctl->OK=1; + ctl->finished=1; +} + + +void MidiPlayer::setPos(ulong gotomsec,MidiStatus *midistat) +{ + int trk,minTrk; + ulong tempo=(ulong)(500000 * ctl->ratioTempo); + double minTime=0,maxTime,prevms=0; + int i,j,likeplaying=1; + + MidiEvent *ev=new MidiEvent; + ctl->SPEVplayed=0; + for (i=0;i<info->ntracks;i++) + { + tracks[i]->init(); + tracks[i]->changeTempo(tempo); + } + + for (i=0;i<16;i++) + { + if (ctl->forcepgm[i]) midistat->chnPatchChange(i, ctl->pgm[i]); + } + + while (likeplaying) + { + trk=0; + minTrk=0; + maxTime=minTime + 120000L; /*milliseconds (2 minutes)*/ + minTime=maxTime; + while (trk<info->ntracks) + { + if (tracks[trk]->absMsOfNextEvent()<minTime) + { + minTrk=trk; + minTime=tracks[minTrk]->absMsOfNextEvent(); + } + trk++; + } + if (minTime==maxTime) + { + likeplaying=0; +#ifdef GENERAL_DEBUG_MESSAGES + printf("END of likeplaying\n"); +#endif + } + else + { + if (minTime>=gotomsec) + { + prevms=gotomsec; + likeplaying=0; +#ifdef GENERAL_DEBUG_MESSAGES + printf("Position reached !! \n"); +#endif + minTime=gotomsec; + } + else + { + prevms=minTime; + } + trk=0; + while (trk<info->ntracks) + { + tracks[trk]->currentMs(minTime); + trk++; + } + } + + if (likeplaying) + { + trk=minTrk; + tracks[trk]->readEvent(ev); + switch (ev->command) + { + /* case (MIDI_NOTEON) : + midistat->noteOn(ev->chn, ev->note, ev->vel);break; + case (MIDI_NOTEOFF): + midistat->noteOff(ev->chn, ev->note, ev->vel);break; + case (MIDI_KEY_PRESSURE) : + midistat->keyPressure(ev->chn, ev->note,ev->vel);break; + */ + case (MIDI_PGM_CHANGE) : + if (!ctl->forcepgm[ev->chn]) midistat->chnPatchChange(ev->chn, ev->patch);break; + case (MIDI_CHN_PRESSURE) : + midistat->chnPressure(ev->chn, ev->vel);break; + case (MIDI_PITCH_BEND) : + midistat->chnPitchBender(ev->chn, ev->d1,ev->d2);break; + case (MIDI_CTL_CHANGE) : + midistat->chnController(ev->chn, ev->ctl,ev->d1);break; + case (MIDI_SYSTEM_PREFIX) : + if ((ev->command|ev->chn)==META_EVENT) + { + if ((ev->d1==5)||(ev->d1==1)) + { + ctl->SPEVplayed++; + } + if (ev->d1==ME_SET_TEMPO) + { + ctl->SPEVplayed++; + tempo=(ulong)(((ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2]))*ctl->ratioTempo); + + midistat->tmrSetTempo((int)tempoToMetronomeTempo(tempo)); + for (j=0;j<info->ntracks;j++) + { + tracks[j]->changeTempo(tempo); + } + } + if (ev->d1==ME_TIME_SIGNATURE) + { + ctl->num=ev->d2; + ctl->den=ev->d3; + ctl->SPEVplayed++; + } + } + break; + } + } + } + delete ev; + ctl->tempo=tempo; +} + + +void MidiPlayer::debugSpecialEvents(void) +{ + SpecialEvent *pspev=spev; + printf("**************************************\n"); + while ((pspev!=NULL)&&(pspev->type!=0)) + { + printf("t:%d ticks:%d diff:%ld abs:%ld s:%s tempo:%ld\n",pspev->type,pspev->ticks,pspev->diffmilliseconds,pspev->absmilliseconds,pspev->text,pspev->tempo); + pspev=pspev->next; + } + +} + +void MidiPlayer::setParseSong(bool b) +{ + parsesong=b; +} + +void MidiPlayer::setGenerateBeats(bool b) +{ + generatebeats=b; +} + +void MidiPlayer::setTempoRatio(double ratio) +{ + if (songLoaded) + { + ctl->ratioTempo=ratio; + parseInfoData(info,tracks,ctl->ratioTempo); + if (parsesong) + { + parseSpecialEvents(); + if (generatebeats) generateBeats(); + + } + } + else + { + ctl->tempo=(ulong)((ctl->tempo*ctl->ratioTempo)/ratio); + ctl->ratioTempo=ratio; + } + +} + +#undef T2MS +#undef MS2T diff --git a/libkmid/player.h b/libkmid/player.h new file mode 100644 index 000000000..6a9210292 --- /dev/null +++ b/libkmid/player.h @@ -0,0 +1,396 @@ +/* player.h - class MidiPlayer. Plays a set of tracks + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _PLAYER_H +#define _PLAYER_H + +#include <libkmid/dattypes.h> +#include <libkmid/midfile.h> +#include <libkmid/deviceman.h> +#include <libkmid/track.h> +#include <libkmid/notearray.h> +#include <kdemacros.h> + +/** + * This struct stores text, lyrics and change tempo events among others. + * + * It includes the main information for an event. That is, the absolute + * millisecond at which this event is played (from the beginning of the song), + * the delta milliseconds from the previous SpecialEvent, an ID, etc. + * + * This struct is used as nodes for a linked list, which you can get using + * MidiPlayer::specialEvents(). + * + * @short Struct used to store certain events + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +struct SpecialEvent +{ + /** + * An integer ID, that is assigned in order to each SpecialEvent. + */ + int id; + + /** + * Delta milliseconds from the previous SpecialEvent. + * + * @see absmilliseconds + */ + ulong diffmilliseconds; + + /** + * The absolute millisecond (from the beginning of the song) at which this + * SpecialEvent object is played. + * + * @see diffmilliseconds + */ + ulong absmilliseconds; + + /** + * MIDI ticks (from the beginning of the song) at which this event is played. + */ + int ticks; + + /** + * Type of event. This currently includes: + * + * @li 0 - Nothing, end of linked list. + * @li 1 - Text Event . See text. + * @li 3 - Change Tempo Event . See tempo. + * @li 5 - Lyrics Event . See text. + * @li 6 - Change number of beats per measure . See num and den. + * @li 7 - Beat . See num and den. + * + * The "Change number of beats per measure" and "beat" events are not really + * in the midi file, but they are added to the linked list in case you have + * an use for it. + */ + int type; + + /** + * Text field . It has a meaning only for Text and Lyrics events. + */ + char text[1024]; + + /** + * Tempo field . It has a meaning only for Change Tempo events. + */ + ulong tempo; + /** + * Numerator . It has a meaning only for Change number of beats per measure and + * beat events. + */ + int num; + /** + * Denominator . It has a meaning only for Change number of beats per measure + * and beat events. + */ + int den; + + /** + * This struct stores text, lyrics and change tempo events among others. + * + * It includes the main information for an event. That is, the absolute + * millisecond at which this event is played (from the beginning of the song), + * the delta milliseconds from the previous SpecialEvent, an ID, etc. + * + * This struct is used as nodes for a linked list, which you can get using + * MidiPlayer::specialEvents(). + */ + struct SpecialEvent *next; + + /** + * Next node in the linked list. + */ + +}; + + +/** + * PlayerController is a struct that is used by the MidiPlayer object + * to tell other parts of the application about the status of the MIDI playing. + * + * @short Struct used to have control over the player engine + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +struct PlayerController +{ + volatile ulong ticksTotal; + volatile ulong ticksPlayed; + volatile double millisecsPlayed; + volatile ulong beginmillisec; + + volatile int tempo; + volatile int num; + volatile int den; + + volatile int SPEVprocessed; + volatile int SPEVplayed; + + /** + * When pause is released, if the caller must know when the player has + * opened the devices and is playing again, then it just has to check + * to see when OK changes the value to 1 + */ + volatile int OK; + + /** + * When the player is playing (or paused), playing is set to 1. + */ + volatile int playing; + + /** + * When the player is paused, paused is set to 1. + */ + volatile int paused; + + /** + * When the player seeking the position of the song, moving is set to 1. + */ + volatile int moving; + + /** + * When the player has finished playing a song, finished is set to 1. + */ + volatile int finished; + + /** + * @internal + * @deprecated + * Not used + */ + volatile int message KDE_DEPRECATED; // set one of the following : + +#define PLAYER_DOPAUSE 1 +#define PLAYER_DOSTOP 2 +#define PLAYER_SETPOS 4 +#define PLAYER_HALT 8 + + volatile ulong gotomsec; //milliseconds to go to,if player_setpos is set + + /** + * When error is 1, an error has ocurred (i.e. it coultn't open the device) + */ + volatile int error; + + /** + * If gm is 1, the song follows the General Midi standard, if gm is 0, the song + * is in MT 32 format. + */ + volatile int gm; + + /** + * 100 means no change, 50 halfs the volume, 200 doubles it (if possible), etc. + * + * @see DeviceManager::setVolumePercentage() + */ + volatile int volumepercentage ; + + /** + * Activate or disactivate the force to use a patch for a given channel. + * @see pgm + */ + volatile bool forcepgm[16]; + + /** + * Force a given patch in each channel at "this" moment, as determined by + * forcepgm. + */ + volatile int pgm[16]; + + /** + * Ratio to multiply the tempo to. + */ + volatile double ratioTempo; + + /** + * @internal Used to stop the main pid until the child has finished to + * send the all notes off event + */ + volatile bool isSendingAllNotesOff; + + volatile MidiEvent *ev; +}; + + +/** + * MIDI file player routines . This class reads a MIDI file and + * play it using a DeviceManager object. + * + * To use it, just call loadSong() with the filename you want to play, + * and then play(). + * + * Please have a look at the note in the play() documentation. + * + * MidiPlayer will write information about the playing process on a + * PlayerController() structure that you must supply to the constructor + * + * Alternatively, if everything you want is to play a midi file in a game or + * any other application that doesn't need to fine tune the midi playing, just + * use the kmidplay() function. + * + * @see KMidSimpleAPI + * + * @short The MIDI file player engine + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class KMID_EXPORT MidiPlayer +{ + class MidiPlayerPrivate; + MidiPlayerPrivate *d; + + DeviceManager *midi; + MidiFileInfo *info; + MidiTrack **tracks; + SpecialEvent *spev; + NoteArray *na; + + int songLoaded; + + PlayerController *ctl; + + bool parsesong; + bool generatebeats; + + void removeSpecialEvents(void); + void parseSpecialEvents(void); + void insertBeat(SpecialEvent *ev,ulong ms,int num,int den); + void generateBeats(void); + + //NoteArray *parseNotes(void); + void debugSpecialEvents(void); + public: + + /** + * Constructor . You must construct and pass a DeviceManager object and a + * PlayerController structure. None of them will be destroyed by this + * object, so you should do it after destroying the MidiPlayer object. + */ + MidiPlayer(DeviceManager *midi_,PlayerController *pctl); + + /** + * Destructor. + */ + ~MidiPlayer(); + + /** + * Loads a Song, and parses it (it the parse wasn't disabled with + * setParseSong() ) . It also generates the Beat events (see + * SpecialEvent::type() ) if you enabled this by using + * setGenerateBeats() . + */ + int loadSong(const char *filename); + + /** + * Unloads the current song, so that every internal variable is empty and clean + * for further usage. + */ + void removeSong(void); + + /** + * Returns true if there's a song already loaded (with a previous call to + * loadSong() ) and false if not. + */ + int isSongLoaded(void) { return songLoaded; } + + /** + * Returns the linked list of SpecialEvents objects . For this to work, + * the parse should be enabled (the default), by using setParseSong(). + */ + SpecialEvent *specialEvents() { return spev; } + + /** + * Returns and array with the notes playen through the song . MidiPlayer must + * parse the song to get the notes, so be sure not to disable the parsing of + * the song. + * + * Returns an array of notes, (just note on and note off events), in the form + * of a NoteArray object + */ + NoteArray *noteArray(void) { return na; } + + + /** + * Plays the song using the DeviceManager object supplied in the + * constructor. It should be already configured, as play doesn't change the + * volume, nor midi mapper, for example. + * + * Note: Calling this function will block the execution of your application + * until the song finishes playing. The solution for this is simple, fork + * before calling it, and create the PlayerController object on shared + * memory. + * + * As alternative, if everything you want is playing a midi file in a game or + * any other application that doesn't need to fine tune the midi playing, just + * use the KMidSimpleAPI::kMidPlay() function. + * + * @see KMidSimpleAPI::kMidInit + * @see KMidSimpleAPI::kMidPlay + * @see KMidSimpleAPI::kMidStop + * @see KMidSimpleAPI::kMidDestruct + */ + void play(bool calloutput=false,void output(void) = 0); + + /** + * Enables or disables the parsing of the song when loading it. This affects + * the SpecialEvents ( specialEvents() ) and the NoteArray + * ( noteArray() ). + */ + void setParseSong(bool b = true); + + /** + * Enables or disables the generation of beats event in a song when loading + * it. + */ + void setGenerateBeats(bool b = false); + + /** + * Returns information about the current MIDI file. + * + * @see loadSong + */ + MidiFileInfo *information(void) { return info; } + + /** + * Sets the position in a song. + * @param gotomsec the number of milliseconds to go to . A subsequent call to + * play() will start playing the song from that moment, instead of the + * beginning. + * @param midistat a MidiStatus object that will contain the status in + * which the MIDI device would be if it would have arrived to this situation by + * a normal playing of the song. + */ + void setPos(ulong gotomsec, class MidiStatus *midistat); + + /** + * Changes the speed at which a song is played. The song's tempo is multiplied + * by the specified ratio. + */ + void setTempoRatio(double ratio); + +}; + +#endif diff --git a/libkmid/sndcard.h b/libkmid/sndcard.h new file mode 100644 index 000000000..c533451ed --- /dev/null +++ b/libkmid/sndcard.h @@ -0,0 +1,88 @@ +/* sndcard.h - include the OSS' soundcard.h file + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ + +#ifndef _SNDCARD_H +#define _SNDCARD_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#ifdef HAVE_SYS_SOUNDCARD_H + #include <sys/soundcard.h> +#elif defined(HAVE_MACHINE_SOUNDCARD_H) + #include <machine/soundcard.h> +#endif + +/* Check for OSS MIDI API */ +#if defined(SNDCTL_SEQ_NRSYNTHS) && defined(CTL_MAIN_VOLUME) + #define HAVE_OSS_SUPPORT +#else + #undef HAVE_OSS_SUPPORT +#endif + +#ifdef HAVE_OSS_SUPPORT + +#ifndef HZ +#define HZ 100 +#endif + +#ifndef MIDI_TYPE_MPU401 +#define MIDI_TYPE_MPU401 0x401 +#endif + +#else + +#define MIDI_NOTEON 0x80 +#define MIDI_NOTEOFF 0x90 +#define MIDI_KEY_PRESSURE 0xA0 +#define MIDI_CTL_CHANGE 0xB0 +#define MIDI_PGM_CHANGE 0xC0 +#define MIDI_CHN_PRESSURE 0xD0 +#define MIDI_PITCH_BEND 0xE0 +#define MIDI_SYSTEM_PREFIX 0xF0 +#define CTL_MAIN_VOLUME 7 + +#define SEQ_DEFINEBUF(a) +#define SEQ_USE_EXTBUF() +#define SEQ_MIDIOUT(a,b) {} +#define SEQ_START_NOTE(a,b,c,d) {} +#define SEQ_STOP_NOTE(a,b,c,d) {} +#define SEQ_SET_PATCH(a,b,c) {} +#define SEQ_CONTROL(a,b,c,d) {} +#define SEQ_BENDER(a,b,c) {} +#define SEQ_CHN_PRESSURE(a,b,c) {} +#define SEQ_KEY_PRESSURE(a,b,c,d) {} +#define SEQ_DUMPBUF() +#define SEQ_WAIT_TIME(a) +#define SEQ_START_TIMER() +#define SEQ_STOP_TIMER() +#define SEQ_CONTINUE_TIMER() + + +#endif + + + +#endif diff --git a/libkmid/synthout.cc b/libkmid/synthout.cc new file mode 100644 index 000000000..069bbd32f --- /dev/null +++ b/libkmid/synthout.cc @@ -0,0 +1,211 @@ +/************************************************************************** + + synthout.cc - class synthOut which handles the /dev/sequencer device + for synths (as AWE32) + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98 Antonio Larrosa Jimenez and P.J.Leonard + 1999,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#include "synthout.h" +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include "sndcard.h" +#include <sys/ioctl.h> +#include <errno.h> +#include <string.h> +#include <sys/param.h> +#include "awe_sup.h" +#include "midispec.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +SEQ_USE_EXTBUF(); + +SynthOut::SynthOut(int d) +{ + seqfd = -1; + devicetype=KMID_SYNTH; + device= d; + _ok=1; +} + +SynthOut::~SynthOut() +{ + closeDev(); +} + +void SynthOut::openDev (int sqfd) +{ + _ok=1; + seqfd = sqfd; + if (seqfd==-1) + { + printfdebug("ERROR: Could not open /dev/sequencer\n"); + return; + } +#ifdef HAVE_OSS_SUPPORT + /* + int i=1; + ioctl(seqfd,SNDCTL_SEQ_THRESHOLD,i); + printfdebug("Threshold : %d\n",i); + */ +#ifdef SYNTHOUTDEBUG + printfdebug("Number of synth devices : %d\n",ndevs); + printfdebug("Number of midi ports : %d\n",nmidiports); + printfdebug("Rate : %d\n",m_rate); +#endif + +#ifdef HAVE_AWE32 + + struct synth_info info; + + // Should really collect the possible devices and let the user choose ? + + info.device = device; + + if (ioctl (seqfd, SNDCTL_SYNTH_INFO, &info) == -1) + printfdebug(" ioctl SNDCTL_SYNTH_INFO FAILED \n"); + + if (info.synth_type == SYNTH_TYPE_SAMPLE + && info.synth_subtype == SAMPLE_TYPE_AWE32) + { + + // Enable layered patches .... + AWE_SET_CHANNEL_MODE(device,1); +#ifdef SYNTHOUTDEBUG + printfdebug(" Found AWE32 dev=%d \n",device); +#endif + } +#endif // HAVE_AWE32 +#endif // HAVE_OSS_SUPPORT + +} + +void SynthOut::closeDev (void) +{ + if (!ok()) return; + //if (seqfd>=0) close(seqfd); + seqfd=-1; +} + +void SynthOut::initDev (void) +{ +#ifdef HAVE_OSS_SUPPORT + int chn; + if (!ok()) return; + uchar gm_reset[5]={0x7e, 0x7f, 0x09, 0x01, 0xf7}; + sysex(gm_reset, sizeof(gm_reset)); + for (chn=0;chn<16;chn++) + { + chnmute[chn]=0; + chnPatchChange(chn,0); + chnPressure(chn,127); + chnPitchBender(chn, 0x00, 0x40); + chnController(chn, CTL_MAIN_VOLUME,127); + chnController(chn, CTL_EXT_EFF_DEPTH, 0); + chnController(chn, CTL_CHORUS_DEPTH, 0); + chnController(chn, 0x4a, 127); + } +#endif +} + +void SynthOut::noteOn (uchar chn, uchar note, uchar vel) +{ + if (vel==0) + { + noteOff(chn,note,vel); + } + else + { + SEQ_START_NOTE(device, map->channel(chn), + map->key(chn,chnpatch[chn],note), + vel); + } +#ifdef SYNTHOUTDEBUG + printfdebug("Note ON >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +void SynthOut::noteOff (uchar chn, uchar note, uchar) +{ + SEQ_STOP_NOTE(device, map->channel(chn), + map->key(chn,chnpatch[chn],note), 0); +#ifdef SYNTHOUTDEBUG + printfdebug("Note OFF >\t chn : %d\tnote : %d\tvel: %d\n",chn,note,vel); +#endif +} + +void SynthOut::keyPressure (uchar chn, uchar note, uchar vel) +{ + SEQ_KEY_PRESSURE(device, map->channel(chn), map->key(chn,chnpatch[chn],note),vel); +} + +void SynthOut::chnPatchChange (uchar chn, uchar patch) +{ + SEQ_SET_PATCH(device,map->channel(chn),map->patch(chn,patch)); + chnpatch[chn]=patch; +} + +void SynthOut::chnPressure (uchar chn, uchar vel) +{ + SEQ_CHN_PRESSURE(device, map->channel(chn) , vel); + chnpressure[chn]=vel; +} + +void SynthOut::chnPitchBender(uchar chn,uchar lsb, uchar msb) +{ + chnbender[chn]=((int)msb<<7) | (lsb & 0x7F); + SEQ_BENDER(device, map->channel(chn), chnbender[chn]); +} + +void SynthOut::chnController (uchar chn, uchar ctl, uchar v) +{ + if ((ctl==11)||(ctl==7)) + { + v=(v*volumepercentage)/100; + if (v>127) v=127; + } + + SEQ_CONTROL(device, map->channel(chn), ctl, v); + chncontroller[chn][ctl]=v; +} + +void SynthOut::sysex(uchar *, ulong ) +{ + // AWE32 doesn't respond to sysex (AFAIK) +/* +#ifndef HAVE_AWE32 + ulong i=0; + SEQ_MIDIOUT(device, MIDI_SYSTEM_PREFIX); + while (i<size) + { + SEQ_MIDIOUT(device, *data); + data++; + i++; + }; + printfdebug("sysex\n"); +#endif +*/ +} diff --git a/libkmid/synthout.h b/libkmid/synthout.h new file mode 100644 index 000000000..4d34336ee --- /dev/null +++ b/libkmid/synthout.h @@ -0,0 +1,118 @@ +/* synthout.h - class synthOut which handles the /dev/sequencer device + for synths (as AWE32) + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98 Antonio Larrosa Jimenez and P.J.Leonard + 1999,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _SYNTHOUT_H +#define _SYNTHOUT_H + +#include <libkmid/midiout.h> + +/** + * Synth (AWE) device output class . SynthOut is used to send MIDI events to + * a general synthesizer, such as AWE synth. + * + * SynthOut inherits MidiOut and supports the same simple API. + * + * The preferred way to use this class is by selecting a synth (or AWE) + * device with MidiManager::setDefaultDevice(), and use a + * MidiManager object. + * + * @short Sends MIDI events to AWE synthesizers + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class SynthOut : public MidiOut +{ + private: + class SynthOutPrivate; + SynthOutPrivate *di; + + public: + /** + * Constructor. See MidiOut::MidiOut() for more information. + */ + SynthOut(int d=0); + + /** + * Destructor. + */ + ~SynthOut(); + + /** + * See MidiOut::openDev() + */ + void openDev (int sqfd); + + /** + * See MidiOut::closeDev() + */ + void closeDev(void); + + /** + * See MidiOut::initDev() + */ + void initDev (void); + + /** + * See MidiOut::noteOn() + */ + void noteOn ( uchar chn, uchar note, uchar vel ); + + /** + * See MidiOut::noteOff() + */ + void noteOff ( uchar chn, uchar note, uchar vel ); + + /** + * See MidiOut::keyPressure() + */ + void keyPressure ( uchar chn, uchar note, uchar vel ); + + /** + * See MidiOut::chnPatchChange() + */ + void chnPatchChange ( uchar chn, uchar patch ); + + /** + * See MidiOut::chnPressure() + */ + void chnPressure ( uchar chn, uchar vel ); + + /** + * See MidiOut::chnPitchBender() + */ + void chnPitchBender ( uchar chn, uchar lsb, uchar msb ); + + /** + * See MidiOut::chnController() + */ + void chnController ( uchar chn, uchar ctl , uchar v ); + + /** + * It's an empty function, as AWE devices don't support System Exclusive + * messages + */ + void sysex ( uchar *data,ulong size); +}; + +#endif diff --git a/libkmid/tests/Kathzy.mid b/libkmid/tests/Kathzy.mid Binary files differnew file mode 100644 index 000000000..f3a857fba --- /dev/null +++ b/libkmid/tests/Kathzy.mid diff --git a/libkmid/tests/Makefile.am b/libkmid/tests/Makefile.am new file mode 100644 index 000000000..58bf5d58b --- /dev/null +++ b/libkmid/tests/Makefile.am @@ -0,0 +1,14 @@ + +INCLUDES = -I$(srcdir)/.. $(all_includes) + +noinst_PROGRAMS = ctest apitest notesoff + +ctest_SOURCES = ctest.c +ctest_LDADD = ../libkmid.la + +apitest_SOURCES = apitest.cc +apitest_LDADD = ../libkmid.la + +notesoff_SOURCES = notesoff.cc +notesoff_LDADD = ../libkmid.la + diff --git a/libkmid/tests/apitest.cc b/libkmid/tests/apitest.cc new file mode 100644 index 000000000..308fe2957 --- /dev/null +++ b/libkmid/tests/apitest.cc @@ -0,0 +1,25 @@ +#include <libkmid/libkmid.h> +#include <unistd.h> +#include <stdio.h> + +int main (int , char **) +{ + printf("Libkmid test2 . (C) 2000 Antonio Larrosa Jimenez . Malaga (Spain)\n"); + printf("Using libkmid from a simple C++ application\n"); + + KMidSimpleAPI::kMidInit(); + KMidSimpleAPI::kMidLoad("Kathzy.mid"); + KMidSimpleAPI::kMidPlay(); + + for (int i=0;i<30;i++) + { + printf("%d/30 seconds\n",i+1); + sleep(1); + }; + + KMidSimpleAPI::kMidStop(); + KMidSimpleAPI::kMidDestruct(); + + return 0; +}; + diff --git a/libkmid/tests/ctest.c b/libkmid/tests/ctest.c new file mode 100644 index 000000000..6e427a2c7 --- /dev/null +++ b/libkmid/tests/ctest.c @@ -0,0 +1,54 @@ +#include <libkmid/libkmid.h> +#include <unistd.h> +#include <stdio.h> + +int main (int argc, char **argv) +{ + int i; + char c; + + printf("Test1 %s. Using libkmid from a simple C application\n",kMidVersion()); + printf("%s\n",kMidCopyright()); + + if (kMidInit()!=0) + { + printf("Error initializing libkmid\n"); + return 0; + } + + if (kMidDevices()!=0) + { + printf("Available devices :\n"); + for (i=0;i<kMidDevices();i++) + { + printf(" %d) %s - %s\n",i,kMidName(i),kMidType(i)); + } + printf("\nSelect one:"); + c=getc(stdin); + } + else + { + printf("There's no available devices. Let's pretend we didn't noticed it\n"); + printf("and start playing.\n"); + c='0'; + } + + kMidSetDevice((int)(c-'0')); + + kMidLoad("Kathzy.mid"); + kMidPlay(); + + for (i=0;i<45;i++) + { + printf("%d/45 seconds\n",i+1); + sleep(1); + } + + kMidStop(); + kMidDestruct(); + + + return 0; + +}; + diff --git a/libkmid/tests/notesoff.cc b/libkmid/tests/notesoff.cc new file mode 100644 index 000000000..15cbb7f73 --- /dev/null +++ b/libkmid/tests/notesoff.cc @@ -0,0 +1,24 @@ +#include <libkmid/libkmid.h> +#include <libkmid/deviceman.h> +#include <unistd.h> +#include <stdio.h> + +int main (int , char **) +{ + printf("Libkmid Notes Off . (C) 2000 Antonio Larrosa Jimenez . Malaga (Spain)\n"); + printf("Using libkmid from a simple C++ application\n"); + + KMidSimpleAPI::kMidInit(); + + kMid.midi->checkInit(); + kMid.midi->openDev(); + kMid.midi->initDev(); + + kMid.midi->allNotesOff(); + kMid.midi->closeDev(); + + KMidSimpleAPI::kMidDestruct(); + + return 0; +}; + diff --git a/libkmid/track.cc b/libkmid/track.cc new file mode 100644 index 000000000..f936906e7 --- /dev/null +++ b/libkmid/track.cc @@ -0,0 +1,566 @@ +/************************************************************************** + + track.cc - class track, which has a midi file track and its events + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ + +#include "track.h" +#include <stdlib.h> +#include "sndcard.h" +#include "midispec.h" +#include "midfile.h" + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#define T2MS(ticks) (((double)ticks)*(double)60000L)/((double)tempoToMetronomeTempo(tempo)*(double)tPCN) + +#define MS2T(ms) (((ms)*(double)tempoToMetronomeTempo(tempo)*(double)tPCN)/((double)60000L)) + +#define PEDANTIC_TRACK +#define CHANGETEMPO_ONLY_IN_TRACK0 +//#define TRACKDEBUG +//#define TRACKDEBUG2 + +MidiTrack::MidiTrack(FILE *file,int tpcn,int Id) +{ + id=Id; + tPCN=tpcn; + currentpos=0; + size=0; + data=0L; + tempo=1000000; + if (feof(file)) + { + clear(); + return; + }; + size=readLong(file); +#ifdef TRACKDEBUG + printf("Track %d : Size %ld\n",id,size); +#endif + data=new uchar[size]; + if (data==NULL) + { + perror("track: Not enough memory ?"); + exit(-1); + } + ulong rsize=0; + if ((rsize=fread(data,1,size,file))!=size) + { + fprintf(stderr,"track (%d): File is corrupt : Couldn't load track (%ld!=%ld) !!\n", id, rsize, size); + size=rsize; + }; + /* + ptrdata=data; + current_ticks=0; + delta_ticks=readVariableLengthValue(); + wait_ticks=delta_ticks; + endoftrack=0; + */ + init(); +} + +MidiTrack::~MidiTrack() +{ + delete data; + endoftrack=1; + currentpos=0; + size=0; +} + +int MidiTrack::power2to(int i) +{ + return 1<<i; +} + +ulong MidiTrack::readVariableLengthValue(void) +{ + ulong dticks=0; + + while ((*ptrdata) & 0x80) + { +#ifdef PEDANTIC_TRACK + if (currentpos>=size) + { + endoftrack=1; + fprintf(stderr, "track (%d) : EndofTrack found by accident !\n",id); + delta_ticks = wait_ticks = ~0; + time_at_next_event=10000 * 60000L; + return 0; + } + else +#endif + { + dticks=(dticks << 7) | (*ptrdata) & 0x7F; + ptrdata++;currentpos++; + } + + } + dticks=((dticks << 7) | (*ptrdata) & 0x7F); + ptrdata++;currentpos++; + +#ifdef PEDANTIC_TRACK + + if (currentpos>=size) + { + endoftrack=1; + fprintf(stderr,"track (%d): EndofTrack found by accident 2 !\n",id); + dticks=0; + delta_ticks = wait_ticks = ~0; + time_at_next_event=10000 * 60000L; + return 0; + } +#endif +#ifdef TRACKDEBUG + printfdebug("track(%d): DTICKS : %ld\n",id,dticks); + usleep(10); +#endif + return dticks; +} + +int MidiTrack::ticksPassed (ulong ticks) +{ + if (endoftrack==1) return 0; + if (ticks>wait_ticks) + { + printfdebug("track (%d): ERROR : TICKS PASSED > WAIT TICKS\n", id); + return 1; + } + wait_ticks-=ticks; + return 0; +} + +int MidiTrack::msPassed (ulong ms) +{ + if (endoftrack==1) return 0; + current_time+=ms; + //fprintf(stderr, "old + %ld = CURR %g ", ms,current_time); + if ( current_time>time_at_next_event ) + { + fprintf(stderr, "track (%d): ERROR : MS PASSED > WAIT MS\n", id); + return 1; + } +#ifdef TRACKDEBUG + if (current_time==time_at_next_event) printfdebug("track(%d): _OK_",id); +#endif + return 0; +} + +int MidiTrack::currentMs(double ms) +{ + if (endoftrack==1) return 0; + current_time=ms; + //printfdebug("CURR %g",current_time); +#ifdef PEDANTIC_TRACK + if (current_time>time_at_next_event) + { + fprintf(stderr,"track(%d): ERROR : MS PASSED > WAIT MS\n", id); + exit(-1); + return 1; + } +#endif + return 0; +} + +void MidiTrack::readEvent(MidiEvent *ev) +{ + int i,j; + if (endoftrack==1) + { + ev->command=0; + return; + } + /* + printfdebug("...... %d\n",id); + printfdebug("current : %g , tane : %g\n",current_time,time_at_next_event); + printfdebug("......\n"); + */ + int skip_event=0; + current_time=time_at_next_event; + if (((*ptrdata)&0x80)!=0) + { + ev->command=(*ptrdata); + ptrdata++;currentpos++; + lastcommand=ev->command; + } + else + { + ev->command=lastcommand; + } + +#ifdef PEDANTIC_TRACK + if (currentpos>=size) + { + endoftrack=1; + delta_ticks = wait_ticks = ~0; + time_at_next_event=10000 * 60000L; + ev->command=MIDI_SYSTEM_PREFIX; + ev->chn=0xF; + ev->d1=ME_END_OF_TRACK; + fprintf(stderr, "track (%d): EndofTrack found by accident 3\n",id); + return; + } +#endif + + ev->chn=ev->command & 0xF; + ev->command=ev->command & 0xF0; + switch (ev->command) + { + case (MIDI_NOTEON) : + ev->note = *ptrdata;ptrdata++;currentpos++; + ev->vel = *ptrdata;ptrdata++;currentpos++; + if (ev->vel==0) + note[ev->chn][ev->note]=FALSE; + else + note[ev->chn][ev->note]=TRUE; + +#ifdef TRACKDEBUG2 + if (ev->chn==6) { + if (ev->vel==0) printfdebug("Note Onf\n"); + else printfdebug("Note On\n"); + }; +#endif + break; + case (MIDI_NOTEOFF) : +#ifdef TRACKDEBUG2 + if (ev->chn==6) printfdebug("Note Off\n"); +#endif + ev->note = *ptrdata;ptrdata++;currentpos++; + ev->vel = *ptrdata;ptrdata++;currentpos++; + note[ev->chn][ev->note]=FALSE; + + break; + case (MIDI_KEY_PRESSURE) : +#ifdef TRACKDEBUG2 + if (ev->chn==6) printfdebug ("Key press\n"); +#endif + ev->note = *ptrdata;ptrdata++;currentpos++; + ev->vel = *ptrdata;ptrdata++;currentpos++; + break; + case (MIDI_PGM_CHANGE) : +#ifdef TRACKDEBUG2 + if (ev->chn==6) printfdebug ("Pgm\n"); +#endif + ev->patch = *ptrdata;ptrdata++;currentpos++; + break; + case (MIDI_CHN_PRESSURE) : +#ifdef TRACKDEBUG2 + if (ev->chn==6) printfdebug ("Chn press\n"); +#endif + ev->vel = *ptrdata;ptrdata++;currentpos++; + break; + case (MIDI_PITCH_BEND) : +#ifdef TRACKDEBUG2 + if (ev->chn==6) printfdebug ("Pitch\n"); +#endif + ev->d1 = *ptrdata;ptrdata++;currentpos++; + ev->d2 = *ptrdata;ptrdata++;currentpos++; + break; + case (MIDI_CTL_CHANGE) : +#ifdef TRACKDEBUG2 + if (ev->chn==6) printfdebug (stderr, "Ctl\n"); +#endif + ev->ctl = *ptrdata;ptrdata++; currentpos++; + ev->d1 = *ptrdata;ptrdata++;currentpos++; + /* + switch (ev->ctl) + { + case (96) : printfdebug("RPN Increment\n");break; + case (97) : printfdebug("RPN Decrement\n");break; + case (98) : printfdebug("nRPN 98 %d\n",ev->d1);break; + case (99) : printfdebug("nRPN 99 %d\n",ev->d1);break; + case (100) : printfdebug("RPN 100 %d\n",ev->d1);break; + case (101) : printfdebug("RPN 101 %d\n",ev->d1);break; + }; + */ + break; + + case (MIDI_SYSTEM_PREFIX) : +#ifdef TRACKDEBUG2 + if (ev->chn==6) printfdebug ("Sys Prefix\n"); +#endif + switch ((ev->command|ev->chn)) + { + case (0xF0) : + case (0xF7) : + ev->length=readVariableLengthValue(); +#ifdef PEDANTIC_TRACK + if (endoftrack) + { + ev->command=MIDI_SYSTEM_PREFIX; + ev->chn=0xF; + ev->d1=ME_END_OF_TRACK; + } + else +#endif + { + ev->data=ptrdata; + ptrdata+=ev->length;currentpos+=ev->length; + } + break; + case (0xFE): + case (0xF8): + // printfdebug("Active sensing\n"); + break; + case (META_EVENT) : + ev->d1=*ptrdata;ptrdata++;currentpos++; + switch (ev->d1) + { + case (ME_END_OF_TRACK) : + i=0; + j=0; + while ((note[i][j]==FALSE)&&(i<16)) + { + j++; + if (j==128) { j=0; i++; }; + } + if (i<16) // that is, if there is any key still pressed + { + ptrdata--;currentpos--; + ev->chn=i; + ev->command=MIDI_NOTEOFF; + ev->note = j; + ev->vel = 0; + note[ev->chn][ev->note]=FALSE; + fprintf(stderr,"Note Off(simulated)\n"); + return; + } + else + { + endoftrack=1; + delta_ticks = wait_ticks = ~0; + time_at_next_event=10000 * 60000L; +#ifdef TRACKDEBUG + printfdebug("EndofTrack %d event\n",id); +#endif + } + break; + case (ME_SET_TEMPO): + ev->length=readVariableLengthValue(); +#ifdef PEDANTIC_TRACK + if (endoftrack) + { + ev->command=MIDI_SYSTEM_PREFIX; + ev->chn=0xF; + ev->d1=ME_END_OF_TRACK; + } + else +#endif + { + ev->data=ptrdata; + ptrdata+=ev->length;currentpos+=ev->length; + // tempo=((ev->data[0]<<16)|(ev->data[1]<<8)|(ev->data[2])); + // ticks_from_previous_tempochange=0; + // time_at_previous_tempochange=current_time; +#ifdef TRACKDEBUG + printfdebug("Track %d : Set Tempo : %ld\n",id,tempo); +#endif +#ifdef CHANGETEMPO_ONLY_IN_TRACK0 + if (id!=0) skip_event=1; +#endif + } + break; + case (ME_TIME_SIGNATURE) : + ev->length=*ptrdata;ptrdata++;currentpos++; + ev->d2=*ptrdata;ptrdata++;currentpos++; + ev->d3=power2to(*ptrdata);ptrdata++;currentpos++; + ev->d4=*ptrdata;ptrdata++;currentpos++; + ev->d5=*ptrdata;ptrdata++;currentpos++; +#ifdef TRACKDEBUG + printfdebug("TIME SIGNATURE :\n"); + printfdebug("%d\n",ev->d2); + printfdebug("---- %d metronome , %d number of 32nd notes per quarter note\n",ev->d4,ev->d5); + printfdebug("%d\n",ev->d3); +#endif + break; + case (ME_TRACK_SEQ_NUMBER) : + case (ME_TEXT) : + case (ME_COPYRIGHT) : + case (ME_SEQ_OR_TRACK_NAME) : + case (ME_TRACK_INSTR_NAME) : + case (ME_LYRIC) : + case (ME_MARKER) : + case (ME_CUE_POINT) : + case (ME_CHANNEL_PREFIX) : + case (ME_MIDI_PORT) : + case (ME_SMPTE_OFFSET) : + case (ME_KEY_SIGNATURE) : + ev->length=readVariableLengthValue(); +#ifdef PEDANTIC_TRACK + if (endoftrack) + { + ev->command=MIDI_SYSTEM_PREFIX; + ev->chn=0xF; + ev->d1=ME_END_OF_TRACK; + } + else +#endif + { + ev->data=ptrdata; + ptrdata+=ev->length;currentpos+=ev->length; + } + break; + default: +#ifdef GENERAL_DEBUG_MESSAGES + fprintf(stderr,"track (%d) : Default handler for meta event " \ + "0x%x\n", id, ev->d1); +#endif + ev->length=readVariableLengthValue(); +#ifdef PEDANTIC_TRACK + if (endoftrack) + { + ev->command=MIDI_SYSTEM_PREFIX; + ev->chn=0xF; + ev->d1=ME_END_OF_TRACK; + } + else +#endif + { + ev->data=ptrdata; + ptrdata+=ev->length;currentpos+=ev->length; + } + break; + } + break; + default : + fprintf(stderr,"track (%d): Default handler for system event 0x%x\n", + id, (ev->command|ev->chn)); + break; + } + break; + default : + fprintf(stderr,"track (%d): Default handler for event 0x%x\n", + id, (ev->command|ev->chn)); + break; + } +#ifdef PEDANTIC_TRACK + if (currentpos>=size) + { + endoftrack=1; + delta_ticks = wait_ticks = ~0; + time_at_next_event=10000 * 60000L; + printfdebug("track (%d): EndofTrack reached\n",id); + } +#endif + if (endoftrack==0) + { + current_ticks+=delta_ticks; + delta_ticks=readVariableLengthValue(); +#ifdef PEDANTIC_TRACK + if (endoftrack) + { + ev->command=MIDI_SYSTEM_PREFIX; + ev->chn=0xF; + ev->d1=ME_END_OF_TRACK; + return; + } +#endif + ticks_from_previous_tempochange+=delta_ticks; + + time_at_next_event=T2MS(ticks_from_previous_tempochange)+time_at_previous_tempochange; + /* + printf("tane2 : %g, ticks : %g, delta_ticks %ld, tempo : %ld\n", + time_at_next_event,ticks_from_previous_tempochange,delta_ticks,tempo); + printf("timeatprevtc %g , curr %g\n",time_at_previous_tempochange,current_time); + */ + wait_ticks=delta_ticks; + + } + if (skip_event) readEvent(ev); +} + + +void MidiTrack::clear(void) +{ + endoftrack=1; + ptrdata=data; + current_ticks=0; + currentpos=0; + + for (int i=0;i<16;i++) + for (int j=0;j<128;j++) + note[i][j]=FALSE; + + delta_ticks = wait_ticks = ~0; + time_at_previous_tempochange=0; + current_time=0; + ticks_from_previous_tempochange=0; + tempo=1000000; + time_at_next_event=10000 * 60000L; + +} + + +void MidiTrack::init(void) +{ + if (data==0L) { clear(); return; }; + endoftrack=0; + ptrdata=data; + current_ticks=0; + currentpos=0; + + for (int i=0;i<16;i++) + for (int j=0;j<128;j++) + note[i][j]=FALSE; + + delta_ticks=readVariableLengthValue(); + if (endoftrack) return; + wait_ticks=delta_ticks; + + + time_at_previous_tempochange=0; + current_time=0; + ticks_from_previous_tempochange=wait_ticks; + tempo=1000000; + time_at_next_event=T2MS(delta_ticks); + //printf("tane1 : %g\n",time_at_next_event); +} + +void MidiTrack::changeTempo(ulong t) +{ + if (endoftrack==1) return; + if (tempo==t) return; + double ticks; + time_at_previous_tempochange=current_time; + ticks=MS2T(time_at_next_event-current_time); + tempo=t; + time_at_next_event=T2MS(ticks)+current_time; + ticks_from_previous_tempochange=ticks; + +} + +/* +double MidiTrack::absMsOfNextEvent (void) +{ + //printf("%d : %g\n",id,time_at_next_event); + return time_at_next_event; +} +*/ + +#undef T2MS +#undef MS2T diff --git a/libkmid/track.h b/libkmid/track.h new file mode 100644 index 000000000..073fc9825 --- /dev/null +++ b/libkmid/track.h @@ -0,0 +1,237 @@ +/* track.h - class track, which has a midi file track and its events + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _TRACK_H +#define _TRACK_H + +#include <stdio.h> +#include <libkmid/dattypes.h> + +/** + * An structure that represents a MIDI event. + * + * @short Represents a MIDI event + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +struct MidiEvent +{ + /** + * MIDI Command + * + * Caution, if a command doesn't use a variable, it may contain garbage. + */ + uchar command; + + /** + * Channel + */ + uchar chn; + + /** + * Note + */ + uchar note; + + /** + * Velocity + */ + uchar vel; + + /** + * Patch (if command was a change patch command) + */ + uchar patch; + + /** + * Patch (if command was a controller command) + */ + uchar ctl; + + /** + * Data 1 + */ + uchar d1; + + /** + * Data 2 + */ + uchar d2; + + /** + * Data 3 + */ + uchar d3; + + /** + * Data 4 + */ + uchar d4; + + /** + * Data 5 + */ + uchar d5; + + /** + * Data 6 + */ + uchar d6; + + /** + * Length of the generic data variable + */ + ulong length; + + /** + * The data for commands like text, sysex, etc. + */ + uchar *data; + +}; + +/** + * Stores a MIDI track. This can be thought of as a list of MIDI events. + * + * The data types used to store the track is similar to how events are + * stored on a MIDI file, but used in a way that allows for faster parses. + * + * This class is used on MidiPlayer::loadSong() to load the song and + * later play it with MidiPlayer::play(). + * + * @short Stores a MIDI track with a simple API + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class MidiTrack +{ + private: + class MidiTrackPrivate; + MidiTrackPrivate *d; + + int id; + + ulong size; + uchar *data; + uchar *ptrdata; + + bool note[16][128]; // Notes that are set on or off by this track + ulong current_ticks; // Total number of ticks since beginning of song + ulong delta_ticks; // Delta ticks from previous event to next event + ulong wait_ticks; // Wait ticks from previous event in other track + // to next event in this track + + ulong currentpos; // Some songs don't have a endoftrack event, so + // we have to see when currentpos > size + int endoftrack; + + ulong readVariableLengthValue(void); + + uchar lastcommand; // This is to run light without overbyte :-) + + + double current_time; // in ms. + double time_at_previous_tempochange; // in ms. + double ticks_from_previous_tempochange; // in ticks + // double time_to_next_event; // in ms. + double time_at_next_event; // in ms. + int tPCN; + ulong tempo; + + int power2to(int i); + + public: + /** + * Constructor. + * @param file the file to read the track from. It should be ready at the + * start of a track. MidiTrack reads just that track and the file is left at + * the end of this track). + * @param tpcn the ticks per cuarter note used in this file. + * @param Id the ID for this track. + */ + MidiTrack(FILE *file,int tpcn,int Id); + + /** + * Destructor + */ + ~MidiTrack(); + + /** + * Makes the iterator advance the given number of ticks. + * + * @return 0 if OK, and 1 if you didn't handle this track well and you + * forgot to take an event (thing that will never happen if you use + * MidiPlayer::play() ). + */ + int ticksPassed (ulong ticks); + + /** + * Makes the iterator advance the given number of milliseconds. + * + * @return 0 if OK, and 1 if you didn't handle this track well and you + * forgot to take an event (thing that will never happen if you use + * MidiPlayer::play() ). + */ + int msPassed (ulong ms); + + /** + * Returns the current millisecond which the iterator is at. + */ + int currentMs (double ms); + + /** + * Returns the number of ticks left for the next event. + */ + ulong waitTicks (void) { return wait_ticks; } + + // ulong waitMs (void) {return time_to_next_event;}; + + /** + * Returns the absolute number of milliseconds of the next event. + */ + double absMsOfNextEvent (void) { return time_at_next_event; } + + /** + * Change the tempo of the song. + */ + void changeTempo(ulong t); + + /** + * Reads the event at the iterator position, and puts it on the structure + * pointed to by @p ev. + */ + void readEvent(MidiEvent *ev); + + /** + * Initializes the iterator. + */ + void init(void); + + /** + * Clears the internal variables. + */ + void clear(void); + +}; + +#endif diff --git a/libkmid/voiceman.cc b/libkmid/voiceman.cc new file mode 100644 index 000000000..8e7863720 --- /dev/null +++ b/libkmid/voiceman.cc @@ -0,0 +1,279 @@ +/************************************************************************** + + voiceman.cc - The VoiceManager class handles a set of voices for synths + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ + +#include "voiceman.h" +#include <stdio.h> +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +VoiceManager::VoiceManager(int totalvoices) +{ + nvoices=totalvoices; + + FirstVoice=new voice; + FirstVoice->id=0; + FirstVoice->channel=0; + FirstVoice->note=0; + FirstVoice->used=0; + FirstVoice->prev=NULL; + + voice *ptrb=FirstVoice; + voice *ptr=NULL; + int i; + for (i=1;i<nvoices;i++) + { + ptr=new voice; + ptrb->next=ptr; + ptr->id=i; + ptr->channel=0; + ptr->note=0; + ptr->used=0; + ptr->prev=ptrb; + ptrb=ptr; + } + LastVoice=ptr; + LastVoice->next=NULL; + LastnotusedVoice=LastVoice; + + VoiceList=new voice *[nvoices]; + ptr=FirstVoice; + for (i=0;i<nvoices;i++) + { + VoiceList[i]=ptr; + ptr=ptr->next; + } + searcher_aid=new voice; +} + +VoiceManager::~VoiceManager() +{ + voice *ptr=FirstVoice; + voice *ptr2; + while (ptr!=NULL) + { + ptr2=ptr->next; + delete ptr; + ptr=ptr2; + } + FirstVoice=NULL; + LastVoice=NULL; + LastnotusedVoice=NULL; + + delete [] VoiceList; + VoiceList=NULL; + + delete searcher_aid; +} + +void VoiceManager::clearLists(void) +{ +#ifdef VOICEMANDEBUG + printf("voicemanager::cleanLists\n"); +#endif + voice *ptr=FirstVoice; + voice *ptr2=FirstVoice; + while (ptr!=NULL) + { + ptr->used=0; + ptr2=ptr; + ptr=ptr->next; + } + LastVoice=ptr2; + LastnotusedVoice=ptr2; + +} + +int VoiceManager::allocateVoice(int chn,int key) +{ + // First, we take the allocated voice out of the first place of the list + if ((LastnotusedVoice!=NULL)&&(LastnotusedVoice->id==FirstVoice->id)) + { +#ifdef VOICEMANDEBUG + printf("Used last voice !\n"); +#endif + LastnotusedVoice=NULL; + } + voice *newvoice=FirstVoice; + FirstVoice=FirstVoice->next; + FirstVoice->prev=NULL; + +#ifdef VOICEMANDEBUG + printf("Allocating id :%d\n",newvoice->id); +#endif + // then we put the allocated voice at the end of the list + LastVoice->next=newvoice; + newvoice->prev=LastVoice; + LastVoice=newvoice; + LastVoice->next=NULL; + + newvoice->channel=chn; + newvoice->note=key; + +#ifdef VOICEMANDEBUG + if (newvoice->used==1) + { + printf("Replacing voice : %d\n",newvoice->id); + } +#endif + newvoice->used=1; + + //dispStat(); + return newvoice->id; +} + +void VoiceManager::deallocateVoice(int id) +{ + voice *delvoice=VoiceList[id]; +#ifdef VOICEMANDEBUG + printf("Deallocating id :%d\n",id); +#endif + if (delvoice->id==LastVoice->id) + { + LastVoice=delvoice->prev; + LastVoice->next=NULL; + + if (LastnotusedVoice==NULL) + { + delvoice->next=FirstVoice; + FirstVoice->prev=delvoice; + FirstVoice=delvoice; + FirstVoice->prev=NULL; + LastnotusedVoice=FirstVoice; + } + else + { + if (LastnotusedVoice->next==NULL) + { + LastnotusedVoice->next=delvoice; + delvoice->prev=LastnotusedVoice; + delvoice->next=NULL; + LastnotusedVoice=delvoice; + LastVoice=delvoice; + } + else + { + delvoice->next=LastnotusedVoice->next; + delvoice->next->prev=delvoice; + delvoice->prev=LastnotusedVoice; + LastnotusedVoice->next=delvoice; + LastnotusedVoice=delvoice; + } + } + } + else + { + if (delvoice->prev!=NULL) + { + delvoice->prev->next=delvoice->next; + delvoice->next->prev=delvoice->prev; + if (LastnotusedVoice==NULL) + { + delvoice->next=FirstVoice; + FirstVoice->prev=delvoice; + FirstVoice=delvoice; + FirstVoice->prev=NULL; + LastnotusedVoice=FirstVoice; } + else + { + if (LastnotusedVoice->next==NULL) + { + LastnotusedVoice->next=delvoice; + delvoice->prev=LastnotusedVoice; + delvoice->next=NULL; + LastnotusedVoice=delvoice; + LastVoice=delvoice; + } + else + { + delvoice->next=LastnotusedVoice->next; + delvoice->next->prev=delvoice; + delvoice->prev=LastnotusedVoice; + LastnotusedVoice->next=delvoice; + LastnotusedVoice=delvoice; + } + } + } + } + delvoice->used=0; + + // dispStat(); +} + +void VoiceManager::initSearch(void) +{ + searcher=searcher_aid; + searcher_aid->prev=LastVoice; +} + +int VoiceManager::search(int chn) +{ + if (searcher==NULL) return -1; + searcher=searcher->prev; + + while (searcher!=NULL) + { + if (searcher->used==0) return -1; + if (searcher->channel==chn) + { + return searcher->id; + } + searcher=searcher->prev; + } + return -1; +} + +int VoiceManager::search(int chn,int note) +{ + if (searcher==NULL) return -1; + searcher=searcher->prev; + while ((searcher!=NULL)) + { + if (searcher->used==0) return -1; + if ((searcher->channel==chn)&&(searcher->note==note)) + { + return searcher->id; + } + searcher=searcher->prev; + } + return -1; +} + +/* +void VoiceManager::dispStat(void) +{ +#ifdef VOICEMANDEBUG + printf("Stats\n"); + voice *ptr=FirstVoice; + while (ptr!=NULL) + { + printf("Voice %d is %s\n",ptr->id,(ptr->used==0)?("off"):("on")); + ptr=ptr->next; + } + if (LastnotusedVoice!=NULL) printf("LnuV = %d\n",LastnotusedVoice->id); +#endif +} +*/ diff --git a/libkmid/voiceman.h b/libkmid/voiceman.h new file mode 100644 index 000000000..79dee9d3d --- /dev/null +++ b/libkmid/voiceman.h @@ -0,0 +1,172 @@ +/* voiceman.h - The VoiceManager class handles a set of voices for synths + This file is part of LibKMid 0.9.5 + Copyright (C) 1997,98,99,2000 Antonio Larrosa Jimenez + LibKMid's homepage : http://www.arrakis.es/~rlarrosa/libkmid.html + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. + + Send comments and bug fixes to Antonio Larrosa <larrosa@kde.org> + +***************************************************************************/ +#ifndef _VOICEMAN_H +#define _VOICEMAN_H + +/** + * @internal + * Manages the voices used by synthesizers. + * + * @short Manages internally the voices used by synth devices. + * @version 0.9.5 17/01/2000 + * @author Antonio Larrosa Jimenez <larrosa@kde.org> + */ +class VoiceManager +{ + private: + class VoiceManagerPrivate; + VoiceManagerPrivate *d; + + /** + * Number of voices managed by this object. + */ + int nvoices; + + /** + * @internal + */ + struct voice + { + int id; + int channel; + int note; + int used; + + struct voice *prev; + struct voice *next; + }; + + /** + * Points to the beginning of the voice list, that is, to + * the older voice which is ready to be used. + */ + voice *FirstVoice; + + /** + * Points to the last voice, that is, the latest (most recently) used voice. + */ + voice *LastVoice; + + /** + * Points to the latest (list order) not used voice, + * that is, to where deallocated voices will be moved. + */ + voice *LastnotusedVoice; + + /** + * Array with pointers to the voices, arranged by ID for allow faster searches. + */ + voice **VoiceList; + + /** + * @internal + * This variable is used to search channels. + */ + voice *searcher; + + /** + * @internal + * An auxiliary variable for simpler searches. + */ + voice *searcher_aid; + + public: + /** + * Cronstructor. + */ + VoiceManager(int totalvoices); + + /** + * Destructor. + */ + ~VoiceManager(); + + /** + * Allocates a voice used in channel @p chn, and playing key @p key + * @return the voice that should be used. + * + * @see deallocateVoice + */ + + int allocateVoice(int chn,int key); + + /** + * Deallocates the voice with ID @p id. + * + * @see allocateVoice + */ + void deallocateVoice(int id); + + /** + * initSearch() must be called always before search() to initialize + * internal variables. + * + * @see search + */ + void initSearch(void); + + /** + * Returns -1 if channel chn is not currently used, or a voice using + * channel @p chn if any. + * + * Calling search repeteadly, will return all the voices using channel + * @p chn, and a -1 after the last one. + * + * @see initSearch + */ + int search(int chn); + //returns -1 if channel chn is not currently used + // Continue searching for more voices which + // use the channel chn + + /** + * This is a convenience function that differs from the above in that it also + * looks for a specific note (the second parameter) + * + * @see initSearch + */ + int search(int chn,int note); + + /** + * Returns the channel that voice @p v is using. + */ + int channel(int v) {return VoiceList[v]->channel;} + + /** + * Returns the note that voice @p v is playing. + */ + int note(int v) {return VoiceList[v]->note;} + + /** + * Returns true or false if the voice @p v is being used or not respectively. + */ + int used(int v) {return VoiceList[v]->used;} + + /** + * Clears the lists of used voices. + */ + void clearLists(void); +}; + +#endif |