📄 emu8000_pcm.c
字号:
/* * pcm emulation on emu8000 wavetable * * Copyright (C) 2002 Takashi Iwai <tiwai@suse.de> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */#include "emu8000_local.h"#include <linux/init.h>#include <sound/initval.h>#include <sound/pcm.h>/* * define the following if you want to use this pcm with non-interleaved mode *//* #define USE_NONINTERLEAVE *//* NOTE: for using the non-interleaved mode with alsa-lib, you have to set * mmap_emulation flag to 1 in your .asoundrc, such like * * pcm.emu8k { * type plug * slave.pcm { * type hw * card 0 * device 1 * mmap_emulation 1 * } * } * * besides, for the time being, the non-interleaved mode doesn't work well on * alsa-lib... */typedef struct snd_emu8k_pcm emu8k_pcm_t;struct snd_emu8k_pcm { emu8000_t *emu; snd_pcm_substream_t *substream; unsigned int allocated_bytes; snd_util_memblk_t *block; unsigned int offset; unsigned int buf_size; unsigned int period_size; unsigned int loop_start[2]; unsigned int pitch; int panning[2]; int last_ptr; int period_pos; int voices; unsigned int dram_opened: 1; unsigned int running: 1; unsigned int timer_running: 1; struct timer_list timer; spinlock_t timer_lock;};#define LOOP_BLANK_SIZE 8/* * open up channels for the simultaneous data transfer and playback */static intemu8k_open_dram_for_pcm(emu8000_t *emu, int channels){ int i; /* reserve up to 2 voices for playback */ snd_emux_lock_voice(emu->emu, 0); if (channels > 1) snd_emux_lock_voice(emu->emu, 1); /* reserve 28 voices for loading */ for (i = channels + 1; i < EMU8000_DRAM_VOICES; i++) { unsigned int mode = EMU8000_RAM_WRITE; snd_emux_lock_voice(emu->emu, i);#ifndef USE_NONINTERLEAVE if (channels > 1 && (i & 1) != 0) mode |= EMU8000_RAM_RIGHT;#endif snd_emu8000_dma_chan(emu, i, mode); } /* assign voice 31 and 32 to ROM */ EMU8000_VTFT_WRITE(emu, 30, 0); EMU8000_PSST_WRITE(emu, 30, 0x1d8); EMU8000_CSL_WRITE(emu, 30, 0x1e0); EMU8000_CCCA_WRITE(emu, 30, 0x1d8); EMU8000_VTFT_WRITE(emu, 31, 0); EMU8000_PSST_WRITE(emu, 31, 0x1d8); EMU8000_CSL_WRITE(emu, 31, 0x1e0); EMU8000_CCCA_WRITE(emu, 31, 0x1d8); return 0;}/* */static voidsnd_emu8000_write_wait(emu8000_t *emu, int can_schedule){ while ((EMU8000_SMALW_READ(emu) & 0x80000000) != 0) { if (can_schedule) { schedule_timeout_interruptible(1); if (signal_pending(current)) break; } }}/* * close all channels */static voidemu8k_close_dram(emu8000_t *emu){ int i; for (i = 0; i < 2; i++) snd_emux_unlock_voice(emu->emu, i); for (; i < EMU8000_DRAM_VOICES; i++) { snd_emu8000_dma_chan(emu, i, EMU8000_RAM_CLOSE); snd_emux_unlock_voice(emu->emu, i); }}/* * convert Hz to AWE32 rate offset (see emux/soundfont.c) */#define OFFSET_SAMPLERATE 1011119 /* base = 44100 */#define SAMPLERATE_RATIO 4096static int calc_rate_offset(int hz){ return snd_sf_linear_to_log(hz, OFFSET_SAMPLERATE, SAMPLERATE_RATIO);}/* */static snd_pcm_hardware_t emu8k_pcm_hw = {#ifdef USE_NONINTERLEAVE .info = SNDRV_PCM_INFO_NONINTERLEAVED,#else .info = SNDRV_PCM_INFO_INTERLEAVED,#endif .formats = SNDRV_PCM_FMTBIT_S16_LE, .rates = SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000, .rate_min = 4000, .rate_max = 48000, .channels_min = 1, .channels_max = 2, .buffer_bytes_max = (128*1024), .period_bytes_min = 1024, .period_bytes_max = (128*1024), .periods_min = 2, .periods_max = 1024, .fifo_size = 0,};/* * get the current position at the given channel from CCCA register */static inline int emu8k_get_curpos(emu8k_pcm_t *rec, int ch){ int val = EMU8000_CCCA_READ(rec->emu, ch) & 0xfffffff; val -= rec->loop_start[ch] - 1; return val;}/* * timer interrupt handler * check the current position and update the period if necessary. */static void emu8k_pcm_timer_func(unsigned long data){ emu8k_pcm_t *rec = (emu8k_pcm_t *)data; int ptr, delta; spin_lock(&rec->timer_lock); /* update the current pointer */ ptr = emu8k_get_curpos(rec, 0); if (ptr < rec->last_ptr) delta = ptr + rec->buf_size - rec->last_ptr; else delta = ptr - rec->last_ptr; rec->period_pos += delta; rec->last_ptr = ptr; /* reprogram timer */ rec->timer.expires = jiffies + 1; add_timer(&rec->timer); /* update period */ if (rec->period_pos >= (int)rec->period_size) { rec->period_pos %= rec->period_size; spin_unlock(&rec->timer_lock); snd_pcm_period_elapsed(rec->substream); return; } spin_unlock(&rec->timer_lock);}/* * open pcm * creating an instance here */static int emu8k_pcm_open(snd_pcm_substream_t *subs){ emu8000_t *emu = snd_pcm_substream_chip(subs); emu8k_pcm_t *rec; snd_pcm_runtime_t *runtime = subs->runtime; rec = kzalloc(sizeof(*rec), GFP_KERNEL); if (! rec) return -ENOMEM; rec->emu = emu; rec->substream = subs; runtime->private_data = rec; spin_lock_init(&rec->timer_lock); init_timer(&rec->timer); rec->timer.function = emu8k_pcm_timer_func; rec->timer.data = (unsigned long)rec; runtime->hw = emu8k_pcm_hw; runtime->hw.buffer_bytes_max = emu->mem_size - LOOP_BLANK_SIZE * 3; runtime->hw.period_bytes_max = runtime->hw.buffer_bytes_max / 2; /* use timer to update periods.. (specified in msec) */ snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_PERIOD_TIME, (1000000 + HZ - 1) / HZ, UINT_MAX); return 0;}static int emu8k_pcm_close(snd_pcm_substream_t *subs){ emu8k_pcm_t *rec = subs->runtime->private_data; kfree(rec); subs->runtime->private_data = NULL; return 0;}/* * calculate pitch target */static int calc_pitch_target(int pitch){ int ptarget = 1 << (pitch >> 12); if (pitch & 0x800) ptarget += (ptarget * 0x102e) / 0x2710; if (pitch & 0x400) ptarget += (ptarget * 0x764) / 0x2710; if (pitch & 0x200) ptarget += (ptarget * 0x389) / 0x2710; ptarget += (ptarget >> 1); if (ptarget > 0xffff) ptarget = 0xffff; return ptarget;}/* * set up the voice */static void setup_voice(emu8k_pcm_t *rec, int ch){ emu8000_t *hw = rec->emu; unsigned int temp; /* channel to be silent and idle */ EMU8000_DCYSUSV_WRITE(hw, ch, 0x0080); EMU8000_VTFT_WRITE(hw, ch, 0x0000FFFF); EMU8000_CVCF_WRITE(hw, ch, 0x0000FFFF); EMU8000_PTRX_WRITE(hw, ch, 0); EMU8000_CPF_WRITE(hw, ch, 0); /* pitch offset */ EMU8000_IP_WRITE(hw, ch, rec->pitch); /* set envelope parameters */ EMU8000_ENVVAL_WRITE(hw, ch, 0x8000); EMU8000_ATKHLD_WRITE(hw, ch, 0x7f7f); EMU8000_DCYSUS_WRITE(hw, ch, 0x7f7f); EMU8000_ENVVOL_WRITE(hw, ch, 0x8000); EMU8000_ATKHLDV_WRITE(hw, ch, 0x7f7f); /* decay/sustain parameter for volume envelope is used for triggerg the voice */ /* modulation envelope heights */ EMU8000_PEFE_WRITE(hw, ch, 0x0); /* lfo1/2 delay */ EMU8000_LFO1VAL_WRITE(hw, ch, 0x8000); EMU8000_LFO2VAL_WRITE(hw, ch, 0x8000); /* lfo1 pitch & cutoff shift */ EMU8000_FMMOD_WRITE(hw, ch, 0); /* lfo1 volume & freq */ EMU8000_TREMFRQ_WRITE(hw, ch, 0); /* lfo2 pitch & freq */ EMU8000_FM2FRQ2_WRITE(hw, ch, 0); /* pan & loop start */ temp = rec->panning[ch]; temp = (temp <<24) | ((unsigned int)rec->loop_start[ch] - 1); EMU8000_PSST_WRITE(hw, ch, temp); /* chorus & loop end (chorus 8bit, MSB) */ temp = 0; // chorus temp = (temp << 24) | ((unsigned int)rec->loop_start[ch] + rec->buf_size - 1); EMU8000_CSL_WRITE(hw, ch, temp); /* Q & current address (Q 4bit value, MSB) */ temp = 0; // filterQ temp = (temp << 28) | ((unsigned int)rec->loop_start[ch] - 1); EMU8000_CCCA_WRITE(hw, ch, temp); /* clear unknown registers */ EMU8000_00A0_WRITE(hw, ch, 0); EMU8000_0080_WRITE(hw, ch, 0);}/* * trigger the voice */static void start_voice(emu8k_pcm_t *rec, int ch){ unsigned long flags; emu8000_t *hw = rec->emu; unsigned int temp, aux; int pt = calc_pitch_target(rec->pitch); /* cutoff and volume */ EMU8000_IFATN_WRITE(hw, ch, 0xff00); EMU8000_VTFT_WRITE(hw, ch, 0xffff); EMU8000_CVCF_WRITE(hw, ch, 0xffff); /* trigger envelope */ EMU8000_DCYSUSV_WRITE(hw, ch, 0x7f7f); /* set reverb and pitch target */ temp = 0; // reverb if (rec->panning[ch] == 0)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -