Having explored for a while about generating code from MATLAB, I am always looking for a project that
fun in the process or the result or both
is not trivial
uses several peripherals on the embedded system
includes some theory background, rather than things like lighting up LEDs
I just got one: building a digital turntable scratch effect generator, or professionally called a Digital Vinyl System (DVS). It is a device so important for a DJ.
Resources used
Initial Thought
I know nothing about how to make a proper scratch sound effect on a turntable. Yes, I have touch one turntable once but I never made any sound.
I think the scratching sound effect is just due to the resample of sound via manual manipulation of the vinyl track location.
This seems confirmed by the Scratching Wiki Page since people can do scratching with tape as well (see youtube link). So there is nothing special in the vinyl, which make me happy.
The plan is
First, figure out if a resample can really make a sound like scratching.
Design a mechanism that do resample in realtime. I do think aliasing is a potential problem, but it may sounds OK. Anyway we are distorting the original sound, why not a bit more? :D
Find a hardware rotation input device.
A direct drive disk motor with encoder on hand might be the right choice. It can turn in the standard vinyl speed at 45 RPM if no manual input is there.
So the motor should be speed controlled with a very low saturation on the output side to make it free to turn by hand.
Implement the resample mechanism in Simulink some how.
Build interfacing blocks for the Stelleris LP so that simulink can generate runnable code.
It is likely that a DAC chip will be necessary for this task as Stellaris do not have a DAC (LAME!), and timer simulated PWM may consume to much CPU time just to output sample. Moreover, DAC mimic-ed by PWM may have poor quality. (who knows)
Alternatively, I can use the LPC2148 on hand. But I remember it does not have a proper counter interface. Crap. Anyway, I will figure this out someday.
Build binary directly from simulink model, hopefully it will work. If not I will make it work.
Sample of the sound will be stored in ROM/Flash of the embedded system, I guess there is room for only one to two seconds worth of audio for either embedded system I planned to use.
Theory Validation
I assumed the scratching sound is mainly coursed by resampling of sound signal because the vinyl rotation angle is manually tweaked by hand. In the tutorial video, I saw that different hand movement will result in different scratch sound patterns. Another doubt I have before is that there may be some extra component in scratching sound effect due to vibration from abnormal movement between vinyl disk and the the pickup device. However, since people can do it in tape, the outcome will likely to be acceptable even if it ignores the potential vibration altogether.
I found a sound sample in WAV file for about 1 sec. It satisfies the criteria said in Angelo's tutorial two:
Sound is clear,
Volume is loud,
Samples have a distinct begin and end.
So CLEAR, LOUD and ISOLATED. Here is my clear-loud-isolated sound sample plotted.
It is about 1 sec long in total and loud some where from 0.25~0.65 sec. Perfect!
By the way it is simple to load a wave file play a sound in MATLAB. Do the following:
[data, fs, bits] = wavread('sound.wav');
sound(data, fs, bits); % fs is sampling rate, bits is number of bits for each sample
I first try to form a triangle wave like index and play the indexed data vector, like this
n = length(data);
tri_idx= ceil(n/2*( pulstran(0:1/fs:1,(0:1/4:1)-1/8,'tripuls',1/4))+1);
plot(tri_idx);
sound(data(tri_idx),fs*1/2,bits);
Turns out the sound effect of this awful. If I tweak the sample frequency in sound command higher, it starts sound like a scratch but actually it is just aliasing caused by too much downsampling.
I looked the Angelo's tutorial video two again and realize that human hand movement will not have such sharp turns. Instead, it may look like a curve, or may be sine wave. I crafted the following code for a quick try.
sinidx2=ceil(n/3+n/3*sin((1:n)/(n/4)*pi))+1;
sinidxhi=ceil(n/3+n/4*sin((1:1.6*n)/(n/6)*pi))+1;
sin_idx = [sinidx2 sinidxhi];
plot(sin_idx)
sound(data(sin_idx),fs*2,bits);
Actually I put two sine wave in the index. One slower and the other faster. The plot of the index curve looks like
I am a bit surprised that this actually started sounds like a proper scratch. Yeah!!! So much for today, I need to work on something else now.
Prototype Implementation Using Lab Equipment
Since the theory validation works out pretty well, I just go ahead to implement it. In my lab, there is a real-time PC powered by xPC Target system, which I use for lots of projects. The PC have an attached Quanser 4ch (Q4) multi-function DAQ card. I will have to use two I/O funcitions of the card, which are analog output and encoder input. I also have some direct drive motors with 100 lines optical incremental encoder.
The plan is that I will use the motor as the spindle of a simulated vinyl disk. Then I can read the encoder value and figure out the rotation position of the vinyl. I can drive the motor using a speed control to mimic the player rotation as well. The automatic rotation is important to simulate some scratch moves, such as drop. The analog output, of course, is for output of sound.
Since xPC Target is usually used in interrupt mode for all the screen scope and host connection to work, high sample frequency as 44100Hz will easily cause it to overrun. I actually think it should be able to handle higher sample frequency, but it will not, at least on my system, sadly. For this reason, I am using sample frequency of 22kHz, almost half of the CD quality 44.1KHz. This sample frequency should be able to handle most voice and music, except for those with very high frequency components.
An old earphone is going be the sound output. I have never used analog output to drive an earphone or speaker, so I put up a very simple Simulink model to test it.
The entire model runs at the audio sample frequency, which is 22KHz here. Initially, I just have a sine wave block with amplitude of 1 (volt), which gives mono-tone sound. I connected the 3.5mm jack of the earphone to analog output of the Q4 and found the output sound is too loud. Obviously I underestimate the output capacity of the analog output. I can tune the gain to analog output down a little bit so solve this, but since analog output is just D/A converter, doing this will sacrifice the resolution. What I did is connecting a 1kOhm resistor in series with earphone. Although the value of resistor is not based on any calculation, and is likely to be not optimal, it works like expected, which means I can hear some normal volume hum sound in different pitch in my earphone now.
Then I put the sound samples in a lookup table and time index it to make the model "play" sound. It works nicely as well. The gain fs=22000 is to convert time in second into sample index. The blocks after gain fs and before lookup table is just module the index with the max index value, so it plays the same sound over and over to annoy myself. Haha.
With this initial experience in playing sound with Q4, I joined the sample part and encoder reading part together with audio output into one Simulink model.
This model handles the resample in runtime with encoder input. The majority of the model runs at a frequency much lower than the audio sample frequency. I call this lower frequency the "table frequency", since it is how often the turntable position (motor encoder position) is sampled. On the far left of the model, the enc_input block read the encoder count and convert into time it represented by the 45 RPM standard vinyl rotation speed. In the middle part before the lookup table, which contains the sound samples, there are blocks to generate an evenly distributed audio sample index series between two sample of encoder position. For example, if at first "table" sample, the encoder is at location representing 100ms, and at the second sample, it is at 111ms, given a sample ratio 10, the output of the blocks in the middle is actually the sample index value representing 100ms, 101ms, 102ms, ... 110ms. Note that the end is not 111 ms, as it is the first sample of next round.
After a very straight forward lookup table, the value is output to Q4 analog output port. Remember in the analog output for audio test, I was using 22KHz sample frequency. The output of the lookup table here is instead an n-dimension vector at 22/n KHz. There are blocks in the DSP System toolbox for this conversion. The frame conversion put vector in a proper frame form, and the following unbuffer block output a single dimensional output at n times higher sample frequency, which is the same as the frequency of analog output block.
After a few minute of playing, I found it works OK, and gives me the scratching-like sound I expected. However, the sound output quality is not that good. One potential reason is that I did not do a proper resample in this model. Lookup table will only do linear interpolation. It is enough if the resample is slower than the audio sample frequency, but not able to get rid of the aliasing problem if the resample is faster than the audio samples.
OK. I cannot help to have another round of play of my "turntable".